version: "3.9" # ClickTrack — full self-hosted stack # Usage: docker compose up -d # First run: docker compose up -d --build services: # ── Next.js application ────────────────────────────────────────────────────── app: build: context: . dockerfile: docker/Dockerfile restart: unless-stopped depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: DATABASE_URL: postgres://clicktrack:${POSTGRES_PASSWORD:-clicktrack}@postgres:5432/clicktrack REDIS_URL: redis://redis:6379 REGISTRY_REPO: ${REGISTRY_REPO:-} REGISTRY_BRANCH: ${REGISTRY_BRANCH:-main} NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-ClickTrack} MUSICBRAINZ_USER_AGENT: ${MUSICBRAINZ_USER_AGENT:-ClickTrack/0.1 (self-hosted)} NODE_ENV: production expose: - "3000" networks: - internal # ── PostgreSQL 16 ───────────────────────────────────────────────────────────── postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: clicktrack POSTGRES_USER: clicktrack POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-clicktrack} volumes: - postgres_data:/var/lib/postgresql/data - ./docker/init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U clicktrack -d clicktrack"] interval: 5s timeout: 5s retries: 10 networks: - internal # ── Redis 7 ─────────────────────────────────────────────────────────────────── redis: image: redis:7-alpine restart: unless-stopped command: redis-server --save 60 1 --loglevel warning volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 networks: - internal # ── Nginx reverse proxy ─────────────────────────────────────────────────────── nginx: image: nginx:1.27-alpine restart: unless-stopped depends_on: - app ports: - "${HTTP_PORT:-80}:80" - "${HTTPS_PORT:-443}:443" volumes: - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro # Mount TLS certificates here for HTTPS (optional): # - /etc/letsencrypt:/etc/letsencrypt:ro networks: - internal # ── Registry sync (optional cron container) ─────────────────────────────────── registry-sync: build: context: . dockerfile: docker/Dockerfile restart: unless-stopped depends_on: postgres: condition: service_healthy environment: DATABASE_URL: postgres://clicktrack:${POSTGRES_PASSWORD:-clicktrack}@postgres:5432/clicktrack REDIS_URL: redis://redis:6379 REGISTRY_REPO: ${REGISTRY_REPO:-} REGISTRY_BRANCH: ${REGISTRY_BRANCH:-main} NODE_ENV: production # Run the sync script on a cron schedule inside the container. # Requires REGISTRY_REPO to be set; the container exits cleanly if not. command: > sh -c ' while true; do node -e " const { syncRegistry } = require(\"./lib/registry/sync\"); syncRegistry().then(r => console.log(\"Sync:\", JSON.stringify(r))).catch(console.error); " || true; sleep ${REGISTRY_SYNC_INTERVAL:-3600}; done ' profiles: - registry networks: - internal volumes: postgres_data: redis_data: networks: internal: driver: bridge