| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- # Run the published image end to end: the signed `ghcr.io` image plus its own
- # Postgres and Redis, a throwaway TLS certificate, and dev-only key files.
- #
- # This mirrors `compose.yaml`, but pulls `ghcr.io/syavne/d-oauth-server:latest`
- # instead of building from the checkout. Local evaluation only: fixed host
- # ports, self-signed TLS, placeholder secrets. Production guidance is in
- # docs/DEPLOYMENT.md.
- #
- # docker compose -f compose.ghcr.yaml up -d --pull always
- # docker compose -f compose.ghcr.yaml port app 8080
- name: d-oauth-ghcr
- services:
- db:
- image: postgres:18-alpine
- environment:
- POSTGRES_USER: oauth
- POSTGRES_PASSWORD: oauth-dev-only
- POSTGRES_DB: oauth
- volumes:
- # postgres 18 keeps PGDATA under a major-version subdirectory, so the
- # volume mounts the parent (a mount on .../data is rejected by the image).
- - pgdata:/var/lib/postgresql
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U oauth -d oauth"]
- interval: 5s
- timeout: 3s
- retries: 20
- redis:
- image: redis:8-alpine
- # The server asserts this contract at startup: noeviction + AOF.
- command: ["redis-server", "--appendonly", "yes", "--maxmemory-policy", "noeviction"]
- volumes:
- - redisdata:/data
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 5s
- timeout: 3s
- retries: 20
- # One-shot: throwaway self-signed server cert for the local TLS listener.
- gencerts:
- image: alpine:3.21
- volumes:
- - certs:/certs
- command:
- - sh
- - -ec
- - |
- apk add --no-cache openssl >/dev/null
- if [ ! -f /certs/tls.crt ]; then
- openssl req -x509 -newkey rsa:2048 -nodes -days 2 \
- -keyout /certs/tls.key -out /certs/tls.crt \
- -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
- fi
- chown 65532:65532 /certs/tls.crt /certs/tls.key
- chmod 600 /certs/tls.key
- chmod 644 /certs/tls.crt
- # One-shot: DEV-ONLY pepper/TOTP/PII key files, shaped like the cluster's
- # 0400 files mounted at /run/secrets. Values are throwaway placeholders;
- # never reuse them outside this local compose file.
- gensecrets:
- image: alpine:3.21
- volumes:
- - devsecrets:/run/secrets
- command:
- - sh
- - -ec
- - |
- if [ ! -f /run/secrets/token-pepper ]; then
- printf '%s' 'dev-only-pepper-ROTATE-IN-PROD-000000' > /run/secrets/token-pepper
- printf '%s' 'dev-only-totp-key-ROTATE-IN-PROD-000' > /run/secrets/totp-encryption-key
- printf '%s' 'dev-only-pii-key-ROTATE-IN-PROD-0000' > /run/secrets/pii-encryption-key
- fi
- chown 65532:65532 /run/secrets/token-pepper /run/secrets/totp-encryption-key /run/secrets/pii-encryption-key
- chmod 400 /run/secrets/token-pepper /run/secrets/totp-encryption-key /run/secrets/pii-encryption-key
- # Exactly-once migration (sqlx advisory lock). Replicas never migrate.
- migrate:
- image: ghcr.io/syavne/d-oauth-server:latest
- entrypoint: ["/d-oauth-server"]
- command: ["migrate"]
- depends_on:
- gencerts:
- condition: service_completed_successfully
- gensecrets:
- condition: service_completed_successfully
- db:
- condition: service_healthy
- redis:
- condition: service_healthy
- environment:
- D_OAUTH_ISSUER: https://localhost:8080
- D_OAUTH_ENVIRONMENT: development
- D_OAUTH_BIND_ADDR: 0.0.0.0:8080
- D_OAUTH_HEALTH_ADDR: 0.0.0.0:8081
- D_OAUTH_TLS__CERT_PATH: /certs/tls.crt
- D_OAUTH_TLS__KEY_PATH: /certs/tls.key
- D_OAUTH_DATABASE__URL: postgres://oauth:oauth-dev-only@db:5432/oauth?sslmode=disable
- D_OAUTH_REDIS__URL: redis://redis:6379
- D_OAUTH_REDIS__DURABILITY: waitaof
- D_OAUTH_TOKEN_PEPPER_FILE: /run/secrets/token-pepper
- D_OAUTH_TOTP_ENCRYPTION_KEY_FILE: /run/secrets/totp-encryption-key
- D_OAUTH_PII_ENCRYPTION_KEY_FILE: /run/secrets/pii-encryption-key
- volumes:
- - certs:/certs:ro
- - devsecrets:/run/secrets:ro
- app:
- image: ghcr.io/syavne/d-oauth-server:latest
- depends_on:
- migrate:
- condition: service_completed_successfully
- gensecrets:
- condition: service_completed_successfully
- db:
- condition: service_healthy
- redis:
- condition: service_healthy
- environment:
- D_OAUTH_ISSUER: https://localhost:8080
- D_OAUTH_ENVIRONMENT: development
- D_OAUTH_BIND_ADDR: 0.0.0.0:8080
- D_OAUTH_HEALTH_ADDR: 0.0.0.0:8081
- D_OAUTH_TLS__CERT_PATH: /certs/tls.crt
- D_OAUTH_TLS__KEY_PATH: /certs/tls.key
- D_OAUTH_DATABASE__URL: postgres://oauth:oauth-dev-only@db:5432/oauth?sslmode=disable
- D_OAUTH_REDIS__URL: redis://redis:6379
- D_OAUTH_REDIS__DURABILITY: waitaof
- D_OAUTH_TOKEN_PEPPER_FILE: /run/secrets/token-pepper
- D_OAUTH_TOTP_ENCRYPTION_KEY_FILE: /run/secrets/totp-encryption-key
- D_OAUTH_PII_ENCRYPTION_KEY_FILE: /run/secrets/pii-encryption-key
- # Fixed host ports, so the configured issuer (https://localhost:8080)
- # matches what you browse. If 8080 or 8081 is already taken, change the
- # mapping here and D_OAUTH_ISSUER together.
- ports:
- - "8080:8080"
- - "8081:8081" # plaintext health listener; local-only, never publish it
- read_only: true
- volumes:
- - certs:/certs:ro
- - devsecrets:/run/secrets:ro
- volumes:
- pgdata:
- redisdata:
- certs:
- devsecrets:
|