Files
plainpages/compose.yml

165 lines
6.0 KiB
YAML

# Base / production config. Run alone with: docker compose -f compose.yml up
# Plain `docker compose up` also merges compose.override.yml for development.
services:
web:
build: .
ports:
- "3000:3000"
# Explicit behaviour toggles (the app is environment-agnostic — see AGENTS.md).
# Supply COOKIE_SECRET / CSRF_SECRET via env; REQUIRE_SECURE_SECRETS refuses dev throwaways.
environment:
CACHE_TEMPLATES: "true"
REQUIRE_SECURE_SECRETS: "true"
# Wait for the identity/permission services the app talks to (config.ts: kratos + keto)
# and for the one-shot bootstrap to seed the admin + JWKS. Hydra is post-MVP (§6) and
# absent from config.ts, so web doesn't gate on it.
depends_on:
bootstrap:
condition: service_completed_successfully
kratos:
condition: service_healthy
keto:
condition: service_healthy
restart: unless-stopped
# Ory's storage only (Kratos/Keto/Hydra) — the web app never connects here.
# init/init.sql creates one database per service. Dev defaults below; supply
# POSTGRES_USER/PASSWORD via env in production.
postgres:
image: postgres:18.4-alpine3.23
environment:
POSTGRES_USER: ${POSTGRES_USER:-ory}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ory}
POSTGRES_DB: ory
volumes:
- ./ory/postgres/init:/docker-entrypoint-initdb.d:ro
- pgdata:/var/lib/postgresql # PG18+: mount the parent, not /data (version-subdir layout)
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-ory} -d ory"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
# Ory Kratos — identity & self-service auth. Config + identity schema in ory/kratos/.
# DSN is the per-service `kratos` DB (init.sql); supply POSTGRES_* via env in prod.
kratos-migrate:
image: oryd/kratos:v26.2.0
depends_on:
postgres:
condition: service_healthy
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/kratos?sslmode=disable
volumes:
- ./ory/kratos:/etc/config/kratos:ro
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
restart: on-failure
kratos:
image: oryd/kratos:v26.2.0
depends_on:
kratos-migrate:
condition: service_completed_successfully
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/kratos?sslmode=disable
volumes:
- ./ory/kratos:/etc/config/kratos:ro
command: serve -c /etc/config/kratos/kratos.yml --watch-courier
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4433/health/ready"]
interval: 5s
timeout: 5s
retries: 20
restart: unless-stopped
# Ory Keto — authorization (ReBAC). Permission model in ory/keto/namespaces.keto.ts (OPL).
# DSN is the per-service `keto` DB (init.sql). The web app calls its read/write APIs (config.ts).
keto-migrate:
image: oryd/keto:v26.2.0
depends_on:
postgres:
condition: service_healthy
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/keto?sslmode=disable
volumes:
- ./ory/keto:/etc/config/keto:ro
command: -c /etc/config/keto/keto.yml migrate up -y
restart: on-failure
keto:
image: oryd/keto:v26.2.0
depends_on:
keto-migrate:
condition: service_completed_successfully
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/keto?sslmode=disable
volumes:
- ./ory/keto:/etc/config/keto:ro
command: serve -c /etc/config/keto/keto.yml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4466/health/ready"]
interval: 5s
timeout: 5s
retries: 20
restart: unless-stopped
# One-command bootstrap (§3, the MVP bar): a one-shot that seeds first-boot state, then
# exits — generate the JWKS if absent, create the demo admin (admin@plainpages.local /
# admin) in Kratos, grant it the `admin` role in Keto. Idempotent, so it re-runs cleanly.
# Runs once kratos+keto are healthy; web waits for it to complete. Tokenizer dir is
# mounted read-write (the only writer) so the absent-JWKS safety net can land the key.
bootstrap:
build: .
depends_on:
kratos:
condition: service_healthy
keto:
condition: service_healthy
environment:
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@plainpages.local}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin}
JWKS_FILE: /etc/config/kratos/tokenizer/jwks.json
KETO_WRITE_URL: http://keto:4467
KRATOS_ADMIN_URL: http://kratos:4434
volumes:
- ./ory/kratos/tokenizer:/etc/config/kratos/tokenizer
command: node src/bootstrap.ts
restart: on-failure
# Ory Hydra — OAuth2/OIDC provider (other apps log in *through* plainpages; README).
# DSN is the per-service `hydra` DB (init.sql). Issuer + login/consent/logout run at
# our app routes (ory/hydra/hydra.yml); the handlers that drive them are §6. Dev
# permits the http issuer via --dev (compose.override.yml); prod supplies an https
# issuer via env (URLS_SELF_ISSUER).
hydra-migrate:
image: oryd/hydra:v26.2.0
depends_on:
postgres:
condition: service_healthy
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/hydra?sslmode=disable
volumes:
- ./ory/hydra:/etc/config/hydra:ro
command: -c /etc/config/hydra/hydra.yml migrate sql -e --yes
restart: on-failure
hydra:
image: oryd/hydra:v26.2.0
depends_on:
hydra-migrate:
condition: service_completed_successfully
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/hydra?sslmode=disable
volumes:
- ./ory/hydra:/etc/config/hydra:ro
command: serve all -c /etc/config/hydra/hydra.yml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4444/health/ready"]
interval: 5s
timeout: 5s
retries: 20
restart: unless-stopped
volumes:
pgdata: