# occupymedia.cc — APP CT (occ-app, CT 150, tailscale 100.78.162.2) # Control room: postgres/redis/temporal + api/scheduler/web/media-worker + the tunnel. # Ports bind 0.0.0.0 in the CT: public access is ONLY via the Cloudflare tunnel # (ingress: app/api → 100.78.162.2). LAN/tailnet can reach them directly. name: occ-app services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER:-occupymedia} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?} POSTGRES_DB: ${POSTGRES_DB:-occupymedia} volumes: [pgdata:/var/lib/postgresql/data] healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-occupymedia}"] interval: 5s retries: 10 restart: unless-stopped redis: image: redis:7-alpine command: ["redis-server", "--appendonly", "yes"] volumes: [redisdata:/data] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s retries: 10 restart: unless-stopped temporal: image: temporalio/auto-setup:1.24 depends_on: postgres: { condition: service_healthy } environment: DB: postgres12 DB_PORT: 5432 POSTGRES_USER: ${POSTGRES_USER:-occupymedia} POSTGRES_PWD: ${POSTGRES_PASSWORD:?} POSTGRES_SEEDS: postgres restart: unless-stopped api: build: context: ../.. dockerfile: infra/occupymedia/Dockerfile args: { APP: api } environment: &appenv PORT: "3000" # Runtime pools use the non-owner role; owner access is offline only. DATABASE_URL: ${DATABASE_URL:?Set the sp_app runtime URL} REDIS_HOST: redis REDIS_URL: redis://redis:6379 POSTGRES_HOST: postgres POSTGRES_PORT: "5432" TEMPORAL_HOST: temporal TEMPORAL_PORT: "7233" TEMPORAL_ADDRESS: "temporal:7233" S3_ENDPOINT: https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com S3_REGION: auto S3_ACCESS_KEY: ${R2_ACCESS_KEY:-unset} S3_SECRET_KEY: ${R2_SECRET_KEY:-unset} S3_BUCKET: occupymedia-media-encrypted S3_KEK: ${S3_KEK:?} SUPERPOSTER_KEK: ${SUPERPOSTER_KEK:?} SESSION_SECRET: ${SESSION_SECRET:?} CORS_ORIGINS: https://app.occupymedia.cc PUBLIC_ORIGIN: https://app.occupymedia.cc WEB_ORIGIN: https://app.occupymedia.cc API_PUBLIC_URL: https://api.occupymedia.cc # OAuth routing: providers redirect the browser to CONNECTIONS_CALLBACK_URL # after consent; the API then lands it on APP_URL. Both must be public — # unset, both default to localhost and the flow breaks silently. APP_URL: https://app.occupymedia.cc CONNECTIONS_CALLBACK_URL: https://api.occupymedia.cc/connections/callback PEERTUBE_INSTANCE_URL: https://tube.occupymedia.cc PEERTUBE_AUTHORIZE_PATH: /oauth/authorize PEERTUBE_TOKEN_PATH: /api/v1/users/token ports: ["3000:3000"] depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } restart: unless-stopped scheduler: build: context: ../.. dockerfile: infra/occupymedia/Dockerfile args: { APP: scheduler } environment: <<: *appenv PORT: "3010" SCHEDULER_DATABASE_URL: ${SCHEDULER_DATABASE_URL:?Set the sp_app runtime URL} SCHEDULER_HEALTH_PORT: "8081" healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8081/readyz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] interval: 10s timeout: 5s start_period: 30s retries: 3 depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } temporal: { condition: service_started } restart: unless-stopped web: build: context: ../.. dockerfile: infra/occupymedia/Dockerfile args: APP: web NEXT_PUBLIC_API_URL: https://api.occupymedia.cc environment: <<: *appenv PORT: "3001" ports: ["3001:3001"] depends_on: [api] restart: unless-stopped media-worker: build: context: ../.. dockerfile: infra/media-worker.Dockerfile environment: <<: *appenv PORT: "3100" MEDIA_WORKER_ID: worker-1 MEDIA_FENCE_PATH: /var/lib/media-worker/fence.json POSTGRES_HOST: postgres REDIS_HOST: redis # cross-CT: peertube RTMP on occ-tube via tailscale PEERTUBE_PUSH_URL: rtmp://100.92.231.98:1935/live volumes: [mediaworkerstate:/var/lib/media-worker] depends_on: redis: { condition: service_healthy } restart: unless-stopped cloudflared: image: cloudflare/cloudflared:latest command: ["tunnel", "--no-autoupdate", "run", "--token", "${CLOUDFLARE_TUNNEL_TOKEN:?}"] restart: unless-stopped volumes: pgdata: redisdata: mediaworkerstate: