# Example compose: one image (built from ./Dockerfile) run as three services, # fronted by a gateway that enforces the path rules: # /api/* -> api # /, /login, /dashboard, … -> frontend (static SPA + assets) # everything else -> redirect # # Bring it up: docker compose up --build # Then visit: http://localhost:8080 # # Anonymous flows work out of the box. For login, set GOOGLE_* / OIDC_* below # (and add the callback URLs http://localhost:8080/api/v1/auth//callback # in your provider console). x-backend-env: &backend-env STORE: postgres DATABASE_URL: postgres://snip:snip@postgres:5432/snip?sslmode=disable REDIS_ADDR: redis:6379 SESSION_SECRET: ${SESSION_SECRET:-change-me-in-prod} PUBLIC_URL: ${PUBLIC_URL:-http://localhost:8080} SHORT_DOMAIN: ${SHORT_DOMAIN:-localhost:8080} POST_LOGIN_REDIRECT: /dashboard COOKIE_SECURE: "false" GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-} GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} # Generic OIDC points at the bundled mock provider (see mock-oidc service). # The issuer host:port must be identical from the browser AND the backend, so # we use the docker service name `mock-oidc:8090` for both. To log in from a # real browser, add this line to your /etc/hosts: 127.0.0.1 mock-oidc OIDC_ISSUER: ${OIDC_ISSUER:-http://mock-oidc:8090/default} OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-snip-web} OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-snip-secret} services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: snip POSTGRES_PASSWORD: snip POSTGRES_DB: snip volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U snip"] interval: 3s timeout: 3s retries: 10 redis: image: redis:7-alpine command: ["redis-server", "--save", "", "--appendonly", "no"] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 3s timeout: 3s retries: 10 migrate: image: snip:latest # built by the `api` service above command: ["migrate"] # runs schema DDL (idempotent) then exits 0 environment: *backend-env depends_on: postgres: { condition: service_healthy } restart: "no" api: build: . image: snip:latest command: ["api"] # serves /api/v1/* on :8080 environment: *backend-env depends_on: migrate: { condition: service_completed_successfully } redis: { condition: service_healthy } mock-oidc: { condition: service_started } healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8080/api/v1/healthz"] interval: 5s timeout: 3s retries: 10 frontend: image: snip:latest # built by the `api` service above command: ["frontend"] # serves the SPA on :8081 environment: *backend-env healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8081/healthz"] interval: 5s timeout: 3s retries: 10 redirect: image: snip:latest # built by the `api` service above command: ["redirect"] # serves short-code redirects on :8082 environment: *backend-env depends_on: migrate: { condition: service_completed_successfully } redis: { condition: service_healthy } healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8082/healthz"] interval: 5s timeout: 3s retries: 10 # Mock OpenID Connect provider for local login. It accepts any client and # auto-issues a token (no real account needed). The service name + port # (mock-oidc:8090) is used as the issuer by BOTH the backend and the browser, # which is why /etc/hosts needs `127.0.0.1 mock-oidc` for browser logins. mock-oidc: image: ghcr.io/navikt/mock-oauth2-server:2.1.10 ports: - "8090:8090" environment: SERVER_PORT: "8090" # Non-interactive: issue a token immediately for a demo identity. JSON_CONFIG: >- { "interactiveLogin": false, "tokenCallbacks": [ { "issuerId": "default", "requestMappings": [ { "requestParam": "grant_type", "match": "authorization_code", "claims": { "sub": "demo-user", "email": "demo@snip.to", "name": "Demo User" } } ] } ] } gateway: image: caddy:2-alpine ports: - "8080:80" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro depends_on: api: { condition: service_healthy } frontend: { condition: service_healthy } redirect: { condition: service_healthy } volumes: pgdata: