# ═══════════════════════════════════════════════════════════════ # IndeeHub — Production Stack for Portainer # ═══════════════════════════════════════════════════════════════ # # All ${VARIABLES} are resolved by Portainer at deploy time. # Configure them in Portainer → Stacks → Environment variables # before deploying. # # See env.portainer for the full list of required variables. # # For local development, use: docker compose -f docker-compose.dev.yml up # ═══════════════════════════════════════════════════════════════ version: '3.8' services: # ── Frontend (nginx serving built Vue app) ────────────────── app: build: context: . dockerfile: Dockerfile args: CACHEBUST: "32" VITE_USE_MOCK_DATA: "false" VITE_CONTENT_ORIGIN: ${FRONTEND_URL} VITE_INDEEHUB_API_URL: /api VITE_INDEEHUB_CDN_URL: /storage VITE_NOSTR_RELAYS: "" restart: unless-stopped ports: - "${APP_PORT:-7755}:7777" depends_on: - relay - api networks: - indeedhub-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:7777/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s # ── Backend API (NestJS) ──────────────────────────────────── api: build: context: ./backend dockerfile: Dockerfile args: CACHEBUST: "13" restart: unless-stopped environment: # ── Core ───────────────────────────────────────────── ENVIRONMENT: production PORT: 4000 DOMAIN: ${DOMAIN} FRONTEND_URL: ${FRONTEND_URL} # ── Database ───────────────────────────────────────── DATABASE_HOST: postgres DATABASE_PORT: 5432 DATABASE_USER: ${POSTGRES_USER} DATABASE_PASSWORD: ${POSTGRES_PASSWORD} DATABASE_NAME: ${POSTGRES_DB} # ── Redis / BullMQ ─────────────────────────────────── QUEUE_HOST: redis QUEUE_PORT: 6379 QUEUE_PASSWORD: ${REDIS_PASSWORD:-} # ── S3 / MinIO ────────────────────────────────────── S3_ENDPOINT: http://minio:9000 AWS_REGION: us-east-1 AWS_ACCESS_KEY: ${S3_ACCESS_KEY} AWS_SECRET_KEY: ${S3_SECRET_KEY} S3_PRIVATE_BUCKET_NAME: indeedhub-private S3_PUBLIC_BUCKET_NAME: indeedhub-public S3_PUBLIC_BUCKET_URL: ${S3_PUBLIC_BUCKET_URL} # ── BTCPay Server ─────────────────────────────────── BTCPAY_URL: ${BTCPAY_URL} BTCPAY_API_KEY: ${BTCPAY_API_KEY} BTCPAY_STORE_ID: ${BTCPAY_STORE_ID} BTCPAY_WEBHOOK_SECRET: ${BTCPAY_WEBHOOK_SECRET} # ── Nostr Auth / JWT ───────────────────────────────── NOSTR_JWT_SECRET: ${NOSTR_JWT_SECRET} NOSTR_JWT_EXPIRES_IN: ${NOSTR_JWT_EXPIRES_IN:-7d} # ── AES-128 Content Encryption ────────────────────── AES_MASTER_SECRET: ${AES_MASTER_SECRET} # ── Admin API ──────────────────────────────────────── ADMIN_API_KEY: ${ADMIN_API_KEY:-} depends_on: postgres: condition: service_healthy redis: condition: service_started minio: condition: service_started networks: - indeedhub-network healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:4000/nostr-auth/health"] interval: 30s timeout: 10s retries: 5 start_period: 60s # ── PostgreSQL Database ───────────────────────────────────── postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres-data:/var/lib/postgresql/data networks: - indeedhub-network healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 start_period: 30s # ── Redis (BullMQ job queue) ──────────────────────────────── redis: image: redis:7-alpine restart: unless-stopped command: > sh -c "if [ -n '${REDIS_PASSWORD:-}' ]; then redis-server --requirepass '${REDIS_PASSWORD}' --appendonly yes; else redis-server --appendonly yes; fi" volumes: - redis-data:/data networks: - indeedhub-network # ── MinIO (S3-compatible object storage) ──────────────────── minio: image: minio/minio:latest restart: unless-stopped command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} volumes: - minio-data:/data networks: - indeedhub-network # ── MinIO bucket init (one-shot: creates required buckets) ── minio-init: image: minio/mc:latest depends_on: - minio entrypoint: > /bin/sh -c " sleep 5; mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}; mc mb local/indeedhub-private --ignore-existing; mc mb local/indeedhub-public --ignore-existing; mc anonymous set download local/indeedhub-public; echo 'MinIO buckets initialized'; " networks: - indeedhub-network restart: "no" # ── FFmpeg Transcoding Worker ─────────────────────────────── ffmpeg-worker: build: context: ./backend dockerfile: Dockerfile.ffmpeg args: CACHEBUST: "13" restart: unless-stopped environment: ENVIRONMENT: production DATABASE_HOST: postgres DATABASE_PORT: 5432 DATABASE_USER: ${POSTGRES_USER} DATABASE_PASSWORD: ${POSTGRES_PASSWORD} DATABASE_NAME: ${POSTGRES_DB} QUEUE_HOST: redis QUEUE_PORT: 6379 QUEUE_PASSWORD: ${REDIS_PASSWORD:-} S3_ENDPOINT: http://minio:9000 AWS_REGION: us-east-1 AWS_ACCESS_KEY: ${S3_ACCESS_KEY} AWS_SECRET_KEY: ${S3_SECRET_KEY} S3_PRIVATE_BUCKET_NAME: indeedhub-private S3_PUBLIC_BUCKET_NAME: indeedhub-public S3_PUBLIC_BUCKET_URL: ${S3_PUBLIC_BUCKET_URL} AES_MASTER_SECRET: ${AES_MASTER_SECRET} depends_on: postgres: condition: service_healthy redis: condition: service_started minio: condition: service_started networks: - indeedhub-network # ── Nostr Relay ───────────────────────────────────────────── relay: image: scsibug/nostr-rs-relay:latest restart: unless-stopped volumes: - relay-data:/usr/src/app/db networks: - indeedhub-network networks: indeedhub-network: driver: bridge volumes: postgres-data: redis-data: minio-data: relay-data: