Skip to main content

Getting Started

Prerequisites

  • Docker or Podman
  • Docker Compose (or Podman Compose)

That's it for running CIS. For local development without containers, see Local development below.

Quick start with pre-built images

The fastest way to get CIS running. Pre-built images are available on Docker Hub — no need to clone the repo or build anything.

Images

ImageDescription
estemendoza/cis:api-latestBackend API (FastAPI + Uvicorn)
estemendoza/cis:frontend-latestFrontend (SvelteKit + nginx)

Versioned tags are also available: estemendoza/cis:api-1.0.0, estemendoza/cis:frontend-1.0.0.

Deploy

  1. Download the deployment compose file and environment template:
curl -O https://raw.githubusercontent.com/estemendoza/cis/main/docker-compose.deploy.yml
curl -O https://raw.githubusercontent.com/estemendoza/cis/main/.env.production.example
cp .env.production.example .env
  1. Edit .env with your production values. Generate secure keys:
# Encryption key for cloud credentials (Fernet)
ENCRYPTION_KEY=$(python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")

# JWT signing key
JWT_SECRET_KEY=$(openssl rand -hex 32)

# Database password
POSTGRES_PASSWORD=$(openssl rand -hex 16)
  1. Start services:
podman-compose -f docker-compose.deploy.yml up -d
  1. Run database migrations:
podman-compose -f docker-compose.deploy.yml run --rm app alembic upgrade head
  1. Open the application:

Architecture

The deployment runs three containers:

  • frontend (nginx) — serves the pre-built SvelteKit app and proxies /api requests to the backend
  • app (uvicorn) — FastAPI backend, runs the scheduler in-process
  • db (postgres:15-alpine) — PostgreSQL database with a persistent volume

Updating

# Pull new images
podman-compose -f docker-compose.deploy.yml pull

# Run migrations with the new image
podman-compose -f docker-compose.deploy.yml run --rm app alembic upgrade head

# Restart services
podman-compose -f docker-compose.deploy.yml up -d

# Clean up old images
podman image prune -f

Bootstrap

On first launch, the app shows a bootstrap wizard to create the initial organization and admin user.

The bootstrap sequence:

  1. Create organization — give your org a name
  2. Create admin user — set email, name, and password
  3. Authenticate — the app logs you in automatically

After bootstrap, the login screen is shown for returning users:

Login screen

Onboarding

Once authenticated, the dashboard shows a guided onboarding flow with four steps to get you up and running:

Dashboard onboarding

The steps are:

  1. Connect a cloud account (AWS, Azure, or GCP)
  2. Sync and discover your instances
  3. Create policies to schedule start/stop
  4. Watch the savings add up

Click the Connect Cloud Account button to get started.

Step 1: Add a cloud account

Navigate to Settings > Cloud Accounts and click + Add Account. If you have no accounts yet, the page shows an empty state prompting you to add your first one:

Settings — empty cloud accounts

Select your cloud provider and enter the required credentials. Each provider requires different fields:

AWS

Provide your IAM Access Key ID and Secret Access Key. You can scope access with a read-only IAM policy or allow start/stop actions. See AWS setup instructions for how to create an IAM user and policy.

Add AWS account

Azure

Provide your Subscription ID, Tenant ID, Client ID (App ID), and Client Secret. Create a service principal in Azure AD with the Virtual Machine Contributor role. See Azure setup instructions for the full walkthrough.

Add Azure account

GCP

Provide your Project ID and a Service Account JSON key. The service account needs compute.instances.list, compute.instances.start, and compute.instances.stop permissions. See GCP setup instructions for how to create a service account and download the key.

Add GCP account

For all providers, you can choose to scan all regions or select specific regions to limit discovery scope. You can also toggle whether the account is active.

Click Add Account to save. The app will validate your credentials and begin discovering instances. For more details on credential requirements and permissions, see the Connecting Cloud Accounts page.

Step 2: Discover resources

After adding a cloud account, CIS automatically syncs and discovers your compute instances. You can view them on the Resources page:

Resources page

Use the filters at the top to narrow by provider, state, or region. Each resource shows its name, current state (running/stopped), region, instance type, and when it was last seen.

Step 3: Create a policy

Navigate to Policies and click Create Policy to define scheduling rules for your instances:

Policies page

Policies define operating windows for your instances. You can use the weekly grid to visually paint hours, or switch to cron expressions for more flexible schedules.

Weekly grid — click and drag to select running hours per day:

Weekly schedule grid

Cron expressions — define start and stop times with standard cron syntax:

Cron expression schedule

Step 4: Monitor savings

Once policies are active and the scheduler is running, the Dashboard will show KPI cards with estimated monthly savings, resource counts, execution success rates, and recent failures. You can also use the Calculator page for detailed per-resource cost projections.

Active dashboard

Local development with Podman Compose

The dev compose stack runs the full application (DB + backend + frontend) with hot reload enabled on both the backend and frontend.

Setup

  1. Clone the repo and create your environment file:
git clone https://github.com/estemendoza/cis.git
cd cis
cp .env.example .env
  1. Edit .env with your local values. At minimum set:
ENCRYPTION_KEY=$(python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
JWT_SECRET_KEY=$(openssl rand -hex 32)
  1. Start the stack:
podman-compose up -d
  1. Apply database migrations:
podman-compose exec app alembic upgrade head
  1. Open the application:

Hot reload

Both services watch for file changes automatically:

  • Backend — the app/ directory is mounted into the container. Uvicorn runs with --reload, so any Python file change restarts the server instantly. File system polling is enabled via WATCHFILES_FORCE_POLLING=true for compatibility with Podman's virtual filesystem.
  • Frontendfrontend/src/ and frontend/static/ are mounted. Vite's HMR updates the browser without a full reload. Polling is enabled via CHOKIDAR_USEPOLLING=true.

Useful commands

# View logs
podman-compose logs -f app
podman-compose logs -f frontend

# Restart a single service after a dependency change
podman-compose restart app

# Rebuild after changing pyproject.toml or package.json
podman-compose up --build -d

# Stop the stack
podman-compose down

Local development without Docker

For contributors or developers who want to run outside containers. Requires Python 3.11+, Poetry 2.x, Node.js 20+, and PostgreSQL 15+.

Backend

poetry install
poetry run alembic upgrade head
poetry run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Frontend

cd frontend
npm ci
npm run dev

Configuration

Key environment variables (see .env.production.example for the full list):

VariableDescription
POSTGRES_SERVERDatabase host
POSTGRES_PORTDatabase port
POSTGRES_USERDatabase user
POSTGRES_PASSWORDDatabase password
POSTGRES_DBDatabase name
ENCRYPTION_KEYEncryption key for cloud credentials
JWT_SECRET_KEYSecret for JWT token signing
PRICING_UPDATE_HOUR_UTCHour (UTC) for automatic pricing refresh
PRICING_UPDATE_MINUTE_UTCMinute for automatic pricing refresh
CORS_ALLOW_ALL_ORIGINSAllow all CORS origins (use with care)