# Local backend storage: persistent Postgres plus Redis for sequence counters, # realtime pub/sub, and long polling. Non-default ports avoid local conflicts. services: postgres: image: postgres:16.3-alpine container_name: open-tag-pg environment: POSTGRES_USER: opentag POSTGRES_PASSWORD: opentag POSTGRES_DB: opentag ports: ["5433:5432"] volumes: ["pgdata:/var/lib/postgresql/data"] restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U opentag -d opentag"] interval: 5s timeout: 3s retries: 10 redis: image: redis:7.2-alpine container_name: open-tag-redis # Default: no auth (suitable for a private network / single-host VPS behind a firewall). # To enable Redis password auth, replace the default `image` line above with: # command: redis-server --requirepass ${REDIS_PASSWORD} # then set REDIS_PASSWORD in .env.docker and update REDIS_URL to: # redis://:${REDIS_PASSWORD}@redis:6379 # See docs/self-host.md "Redis password hardening" for the full procedure. ports: ["6380:6379"] volumes: ["redisdata:/data"] restart: unless-stopped healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 # Control plane (API + web). Opt-in via `--profile app` so `npm run infra` (no profile) still brings up # only postgres+redis for host-run development. The daemon is NOT here — run it on your host (see docs/docker.md). app: build: { context: ., dockerfile: Dockerfile } image: open-tag-app:latest container_name: open-tag-app profiles: ["app"] env_file: [.env.docker] # copy .env.docker.example → .env.docker and fill the secrets first ports: ["${APP_PORT:-7788}:7788"] depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } restart: unless-stopped volumes: pgdata: {} redisdata: {}