kaveo · Get started
Quick start
Bring up the five-container kaveo stack with Docker Compose, sign in as the bootstrap owner, load the demo dataset and run your first scan.
On this page
This guide takes you from a kaveo distribution to a scan full of findings. You set a few values in .env, start the Docker Compose stack, sign in as the owner and load a synthetic demo account. You do not need an AWS account for any of it.
Requirements#
- Docker with the Compose plugin (
docker compose) openssl, to generate secrets- Ports 80 and 443 free on the host. Caddy publishes both. To change the host side, set
KAVEO_HTTP_PORTandKAVEO_HTTPS_PORTin.env. makeis optional. It works only with the source tree, and the note in step 2 explains how to use it.
Run every command from the root of the distribution, where .env.example lives.
1. Configure the environment#
Copy the example environment file:
cp .env.example .env
Generate three random values. Run this once per secret:
openssl rand -hex 24
Set these values in .env, and use your own address for the owner account. KAVEO_SUPERADMIN_EMAILS is not in the example file, so add that line yourself.
POSTGRES_PASSWORD=<generated value 1>
POSTGRES_APP_PASSWORD=<generated value 2>
KAVEO_BOOTSTRAP_PASSWORD=<generated value 3>
KAVEO_BOOTSTRAP_EMAIL=you@example.com
KAVEO_SUPERADMIN_EMAILS=you@example.com
KAVEO_SESSION_COOKIE_SECURE=false
Compose will not start until all three passwords are set. Each one uses the ${VAR:?} form, which has no fallback value.
Set them before the first start. Postgres applies the two database passwords only when it initializes an empty data volume. kaveo creates the owner only while the database has no users. If you change these values in .env later, the existing passwords stay the same.
2. Start the stack#
docker compose --env-file .env -f infra/docker-compose.yml up -d --build
The first run builds the api, worker and web images. Five containers start:
When the api boots, it applies any pending migrations while it holds a Postgres advisory lock, so two migration runs never overlap. Compose sets KAVEO_BACKUP_BEFORE_MIGRATE=true, so whenever migrations are pending, the api takes a pg_dump snapshot first. Next it creates the owner account if the database has no users. The worker starts only after the api reports healthy.
Check the services, and wait until api shows as healthy:
docker compose --env-file .env -f infra/docker-compose.yml ps
From the air-gapped bundle#
Check the tarball, unpack it, check every file inside it, then load the images:
shasum -a 256 -c kaveo-airgap-<version>.tar.gz.sha256
mkdir kaveo-airgap && tar -xzf kaveo-airgap-<version>.tar.gz -C kaveo-airgap
cd kaveo-airgap
shasum -a 256 -c SHA256SUMS
docker load -i images.tar
A checksum mismatch means the bundle changed in transit. Stop and ask for a new copy. If everything matches, set up .env as in step 1 and start the stack without building:
docker compose --env-file .env -f infra/docker-compose.yml up -d
3. Sign in#
Open http://localhost. If you changed KAVEO_HTTP_PORT, add that port. Sign in with the KAVEO_BOOTSTRAP_EMAIL and KAVEO_BOOTSTRAP_PASSWORD from your .env.
The owner has the admin role. Because you also listed its address in KAVEO_SUPERADMIN_EMAILS, the sidebar shows Platform (all orgs) under Workspace. There is no data yet. The next step loads some.
4. Load demo data#
make seed
This loads the demo from the api image. It creates a separate workspace named Kaveo Demo Org (slug kaveo-demo) and adds one deliberately insecure synthetic AWS account to it, synthetic-vulnerable-account. It also adds a completed scan with its resources, evidence rows, graph edges and the findings every detector produces over them. If you run it again, it replaces the demo scan with a fresh copy.
To open it:
- In the sidebar, open Platform (all orgs).
- On the
Kaveo Demo Orgrow, choose Act in this org. The console switches to that workspace and opens the dashboard. - In the top bar, select
synthetic-vulnerable-accountif it is not already selected. The seeded scan is already there.
You can also press Run scan. No real role exists for the seeded account. With the default KAVEO_SCAN_MODE=auto and no AWS base credentials, the worker falls back to the same synthetic fixture and labels the scan synthetic in its coverage. If kaveo does have AWS credentials, from .env or an instance role, the role assumption fails, and so does the scan.
5. Explore the console#
- Dashboard. The overview of the selected scan: posture figures, the fix to make first, attack-surface and compliance summaries, and the severity trend.
- Risk Report. The report view. In the default
real_riskmode it collapses hygiene findings into a count, unless the resource is internet-exposed or sits on a toxic path. The API and the compliance report still return every finding. - Compliance. Shows control results across 10 frameworks. You can export them as CSV, JSON, an evidence worksheet or PDF.
- Attack Paths. Shows the paths an attacker could take from the internet to a high-value resource, traced over the IAM and reachability graph, and the choke points that cut the most of them.
- Remediations. Holds the approval queue for proposed fixes. Only admins can approve or reject. An approved fix runs as an offline dry run unless you set up the AWS executor. See Remediation.
Open any finding to see the evidence behind it.
6. Useful commands#
Each command below starts with docker compose --env-file .env -f infra/docker-compose.yml.
To take a backup, run the backup command from the api image using the migration connection (MIGRATE_DATABASE_URL), as documented in the release README and AIRGAP.md.
Row-level security scopes the app role to one workspace at a time, and pg_dump needs a role that can read every row. The make backup target connects as the app role, so use the command above instead. It writes a timestamped archive to the kaveo_backups volume.
The stock Caddyfile doesn't route /health. To check it from inside the Compose network, run:
docker compose --env-file .env -f infra/docker-compose.yml exec api \
python -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000/health').read().decode())"
The response includes status, version, ai_provider, database, and the number of detectors and collectors that loaded. The Compose healthcheck probes the same endpoint. It returns 200 even when the database is unreachable, so if something looks wrong, check that database reads up.
Next steps#
- Connect cloud accounts: deploy the read-only role and run a real scan
- Deployment: hostnames and TLS, backups and production settings
- Configuration: AI providers, integrations and detection tuning
- How kaveo works: how a scan turns into findings and evidence