DeployProduction readiness
Dev vs prod
Field-by-field differences between compose.dev.yml and compose.prod.yml
Olympus has two Compose stack variants. This page lists every meaningful difference between them.
| Concern | Dev (compose.dev.yml) | Prod (compose.prod.yml) |
|---|---|---|
| App source | volume: mount from sibling repos (../athena, ../hera, ../site) for live reload | Pulled from ghcr.io/olympusoss/<app>@sha256:... pinned by digest |
| Image source for Kratos/Hydra | oryd/kratos:v1.x, oryd/hydra:v2.x | Same upstream Ory images, pinned by digest |
| TLS | Self-signed by Caddy, valid for localhost.olympus.app | Real cert from Let's Encrypt, ACME-HTTP-01 |
| Captcha (Turnstile) | Disabled (TURNSTILE_DISABLED=true) | Enabled if TURNSTILE_SITE_KEY is configured |
Captured by MailSlurper at localhost:5434 | Routed to the configured provider (Resend, Postmark, Brevo, SMTP2GO, custom SMTP) | |
| Postgres | Single self-hosted Postgres container | Managed (Neon recommended) or self-hosted, with sslmode=verify-full |
| Postgres SSL mode | disable | verify-full, see Database SSL verify-full |
| Email verification | Optional (configurable, often left off for dev convenience) | Mandatory, enforced by verify-email-enforcement.yml CI gate |
| Seeded admin | admin@demo.user / admin123! | Operator-supplied via Daedalus Accounts wizard step |
| pgAdmin | localhost:5433 | Same path, but behind Caddy and OAuth SSO |
| MailSlurper | localhost:5434 | Not present |
Kratos serve.public URL | http://localhost:3100, http://localhost:4100 | https://ciam.<domain>/.ory/kratos, https://iam.<domain>/.ory/kratos |
Hydra urls.self.issuer | http://localhost:3102, http://localhost:4102 | https://ciam.<domain>, https://iam.<domain> |
Kratos leak_sensitive_values | true (dev: helpful errors) | false, required by verify-prod-config.yml |
| Hera CIAM host port | Bound via Caddy on :3000 (only Caddy gets a port mapping) | Not bound at all, internal-only, accessed via Caddy's reverse_proxy |
| Hera IAM host port | Bound via Caddy on :4000 (only Caddy gets a port mapping) | Not bound at all |
| Logs | Container stdout (visible via podman logs) | Container stdout, plus shipped to your logging provider |
| Container restart | always for dev convenience | unless-stopped plus systemd unit for VPS-level resilience |
| Image rebuild | octl deploy triggers a build if local source changed | Image is built in GitHub Actions and pushed to GHCR; deploy pulls |
| Compose network | intranet (bridge) | Same; ports policy enforced |
ENCRYPTION_KEY | Generated locally by octl deploy, written to .env.dev | Generated by Daedalus Secrets wizard step, stored in GitHub Secrets |
SESSION_SIGNING_KEY | Generated locally | Generated by Daedalus, stored in GitHub Secrets |
CIAM_RELOAD_API_KEY | Generated locally | Generated by Daedalus, rotated quarterly per Operate, Reload API Key Rotation |
TURNSTILE_SECRET_KEY | Not set (captcha disabled) | Set per the Turnstile provider account |
| Outbound to internet | Allowed but unused (MailSlurper catches mail) | Required for Let's Encrypt ACME, email provider API, GHCR pulls |
What's the same
- The Kratos and Hydra container images are identical between dev and prod (only the config differs).
- The Athena and Hera app code is identical between dev and prod (only the runtime config differs, env vars, image source).
- The database schemas, identity schemas, and migrations are identical.
- The Caddyfile structure is similar (dev uses self-signed; prod uses Let's Encrypt; the route blocks are otherwise the same shape).
Where these differences are configured
| Difference | Configured in |
|---|---|
| App image source | Compose image: vs build: |
| TLS source | Caddyfile tls internal vs tls operator@your-domain |
| Captcha | TURNSTILE_DISABLED env var |
Kratos courier.smtp.connection_uri | |
| Postgres URL | DATABASE_URL env var, including ?sslmode=verify-full |
| Seeded admin | dev/iam-seed-dev.sh runs in dev only |
| Per-domain URLs | Kratos/Hydra config files (platform/{dev,prod}/{ciam,iam}-kratos/kratos.yml) |
Validation
The platform repo's CI runs three workflows that enforce prod-only invariants:
verify-prod-config.yml, checkskratos.yml/hydra.ymlfor unsafe values (leak_sensitive_values: true,disable_self_service, etc).verify-image-pins.yml, every prod image must be referenced by digest, not by tag.verify-email-enforcement.yml, email verification must be mandatory.
Where next
- Deploy, Kratos Production Config, the prod Kratos hardening details.
- Operate, Network Topology, host-bound vs internal-only ports.
- Reference, Compose Services, per-service generated reference.