Prerequisites
- Install Docker Engine with Docker Compose.
- Clone Pantheon and open the
apidirectory. - Create an OpenRouter API key for model calls.
- Create a Daytona API key only when an agent requires a sandbox.
Create the Compose file
Save this file as docker-compose.yaml in the api directory.
services:
postgres:
image: pgvector/pgvector:pg15
environment:
POSTGRES_USER: ai_user
POSTGRES_PASSWORD: secret
POSTGRES_DB: pantheon_db
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ai_user -d pantheon_db"]
interval: 2s
timeout: 3s
retries: 15
migrate:
build:
context: .
target: runtime
image: pantheon:latest
env_file: [.env]
environment:
POSTGRES_DATABASE_URL: postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db
depends_on:
postgres:
condition: service_healthy
command: alembic upgrade head
restart: "no"
api:
build:
context: .
target: runtime
image: pantheon:latest
env_file: [.env]
environment:
POSTGRES_DATABASE_URL: postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db
ports:
- "8000:8000"
depends_on:
migrate:
condition: service_completed_successfully
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
restart: unless-stopped
worker:
image: pantheon:latest
env_file: [.env]
environment:
POSTGRES_DATABASE_URL: postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db
depends_on:
migrate:
condition: service_completed_successfully
command: python -m app.modules.workers.runtime_worker
restart: unless-stopped
volumes:
pgdata:
| Service | Purpose |
|---|---|
| postgres | Stores Pantheon data in the pgdata volume. |
| migrate | Applies Alembic migrations before the API or worker starts. |
| api | Serves the REST API on host port 8000. |
| worker | Claims queued runs and executes agents. |
Set the environment
Save the required values in api/.env. Restrict access to this file.
OPENROUTER_API_KEY=replace-with-your-openrouter-secret
DAYTONA_API_KEY=
POSTGRES_DATABASE_URL=postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db
PANTHON_BOOTSTRAP_ADMIN_KEY=ak_replace-with-a-long-random-secret
| Variable | Purpose | Secret |
|---|---|---|
OPENROUTER_API_KEY | Authorizes model calls. | Yes |
DAYTONA_API_KEY | Creates sandbox environments. Leave it empty when requires_sandbox is false. | Yes |
POSTGRES_DATABASE_URL | Connects each Pantheon service to PostgreSQL. | Yes |
PANTHON_BOOTSTRAP_ADMIN_KEY | Seeds the first admin key when the API starts. | Yes |
Start Pantheon
Build and start
Compose waits for PostgreSQL, runs migrations, then starts the API and worker.
Check each service
The migration service should exit successfully. The API and worker should remain running.
Protect the admin key
The API stores its hash on first start. Keep the raw bootstrap value in a secret manager.
cd /path/to/pantheon/api
docker compose up --build -d
docker compose ps
docker compose logs migrate api worker
Create the first tenant
Admin API
Use the bootstrap admin key to create a tenant and its first API key.
export PANTHEON_BASE_URL="http://localhost:8000"
export PANTHEON_API_KEY="PANTHEON_API_KEY"
TENANT_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/admin/tenants" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Acme","slug":"acme"}')
TENANT_ID=$(printf '%s' "$TENANT_JSON" | jq -r '.id')
KEY_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/admin/tenants/$TENANT_ID/api-keys" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Development","permissions":["*"]}')
printf '%s' "$KEY_JSON" | jq '{id, tenant_id, name, permissions, key_preview, raw_key, created_at}'
The key creation response contains raw_key once.
{
"id": "key_123",
"tenant_id": "tenant_123",
"name": "Development",
"permissions": ["*"],
"key_preview": "pk_example...",
"raw_key": "PANTHEON_API_KEY",
"created_at": "2026-09-04T12:00:00Z"
}
Dashboard
After the auth release, open /dashboard/signup.html.
The dashboard talks to the API that served it, so no base URL setup is needed.
Sign up with an email, password, and organization name. Then create a key on the keys page.
Read Get an API key for the complete flow.
Upgrade
Back up PostgreSQL first. Then pull the source, rebuild images, migrate, and restart services.
cd /path/to/pantheon/api
git pull --ff-only
docker compose build --pull
docker compose run --rm migrate
docker compose up -d --remove-orphans
Back up PostgreSQL
Create a compressed dump outside the database container.
cd /path/to/pantheon/api
docker compose exec -T postgres \
pg_dump -U ai_user -d pantheon_db -Fc > pantheon.dump
Store the dump away from the Docker host. Test restoration on a separate database.
Troubleshooting
| Symptom | Cause | What to do |
|---|---|---|
| Migrations wait for a lock. | Idle database connections still hold the migration lock. | Stop the API and worker. Close idle connections, then run the migration again. |
| The API cannot bind port 8000. | Another process uses the host port. | Stop that process or change the left side of 8000:8000. |
| Sandbox creation fails. | The Daytona account reached its quota. | Increase the quota or use an agent with requires_sandbox set to false. |
Pitfalls
- The migration service keeps running: Inspect its logs before starting the API manually.
- The worker cannot call a model: Set OPENROUTER_API_KEY in the shared .env file and restart the worker.
- A sandbox request uses mock mode: Set DAYTONA_API_KEY before using an agent that requires a sandbox.