# ============================================================================= # SocialiseIT Production Docker Compose # Designed for Portainer deployment with git-based pulls # ============================================================================= # # USAGE: # 1. Create stack.env from stack.env.example with your values # 2. Deploy via Portainer: Stacks -> Add Stack -> Repository # 3. Set compose file: docker-compose.portainer.yml # 4. Add environment variables from stack.env # 5. Disable Re-pull image; app images are built from the repository locally # # REQUIREMENTS: # - Docker Standalone with Compose support for pull_policy: build (not Swarm) # - External network 'proxy-net' must exist: docker network create proxy-net # - Nginx Proxy Manager configured to forward traffic # ============================================================================= services: # --------------------------------------------------------------------------- # Init Container -- generates secrets on first boot, then exits # --------------------------------------------------------------------------- init: image: alpine:3.19 container_name: socialiseit-init command: sh /docker-init.sh volumes: - ./docker-init.sh:/docker-init.sh:ro - secrets_data:/secrets restart: "no" # --------------------------------------------------------------------------- # Next.js Web Application (Production) # --------------------------------------------------------------------------- webapp: build: context: . dockerfile: Dockerfile target: webapp # Rebuild from the checked-out source on each deployment (cached layers reused). pull_policy: build container_name: socialiseit-webapp restart: unless-stopped networks: - default - proxy-net # NO external ports - traffic routed through Nginx Proxy Manager environment: # Database - DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@socialiseit-postgres:5432/${POSTGRES_DB:-socialiseit} - POSTGRES_PRISMA_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@socialiseit-postgres:5432/${POSTGRES_DB:-socialiseit} - POSTGRES_URL_NON_POOLING=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@socialiseit-postgres:5432/${POSTGRES_DB:-socialiseit} # Redis (with password authentication) - REDIS_URL=redis://:${REDIS_PASSWORD:-socialiseit_redis}@socialiseit-redis:6379 # Application - NODE_ENV=production - NEXTAUTH_URL=${APP_URL} - NEXTAUTH_SECRET=${AUTH_SECRET} - AUTH_SECRET=${AUTH_SECRET} # Encryption (CRITICAL: Keep consistent across deployments!) - ENCRYPTION_KEY=${ENCRYPTION_KEY} # MinIO/S3 Storage - S3_ENDPOINT=${S3_ENDPOINT:-http://socialiseit-minio:9000} - S3_PUBLIC_URL=${S3_PUBLIC_URL:-} - S3_ACCESS_KEY=${MINIO_ROOT_USER:-minioadmin} - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD:-minioadmin} - S3_BUCKET=${S3_BUCKET:-socialiseit} # OAuth Providers - AUTH_GOOGLE_ID=${AUTH_GOOGLE_ID:-} - AUTH_GOOGLE_SECRET=${AUTH_GOOGLE_SECRET:-} # Social Platform APIs - INSTAGRAM_CLIENT_ID=${INSTAGRAM_CLIENT_ID:-} - INSTAGRAM_CLIENT_SECRET=${INSTAGRAM_CLIENT_SECRET:-} - FACEBOOK_APP_ID=${FACEBOOK_APP_ID:-} - FACEBOOK_APP_SECRET=${FACEBOOK_APP_SECRET:-} - TIKTOK_CLIENT_KEY=${TIKTOK_CLIENT_KEY:-} - TIKTOK_CLIENT_SECRET=${TIKTOK_CLIENT_SECRET:-} - YOUTUBE_API_KEY=${YOUTUBE_API_KEY:-} - YOUTUBE_CLIENT_ID=${YOUTUBE_CLIENT_ID:-} - YOUTUBE_CLIENT_SECRET=${YOUTUBE_CLIENT_SECRET:-} # AI Services - OPENAI_API_KEY=${OPENAI_API_KEY:-} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} depends_on: init: condition: service_completed_successfully postgres: condition: service_healthy redis: condition: service_healthy volumes: # Persist uploaded media files across container rebuilds - uploads_data:/app/public/uploads - secrets_data:/secrets:ro healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:3000/api/health" ] interval: 30s timeout: 10s retries: 3 start_period: 40s deploy: resources: limits: memory: 1G cpus: '1.0' # --------------------------------------------------------------------------- # Background Job Worker # --------------------------------------------------------------------------- worker: build: context: . dockerfile: Dockerfile target: worker pull_policy: build container_name: socialiseit-worker restart: unless-stopped networks: - default environment: - DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@socialiseit-postgres:5432/${POSTGRES_DB:-socialiseit} - REDIS_URL=redis://:${REDIS_PASSWORD:-socialiseit_redis}@socialiseit-redis:6379 - ENCRYPTION_KEY=${ENCRYPTION_KEY} - NODE_ENV=production - NEXTAUTH_URL=${APP_URL} # Required for file_url video uploads (Facebook downloads from this URL) - APP_URL=${APP_URL} depends_on: init: condition: service_completed_successfully postgres: condition: service_healthy redis: condition: service_healthy webapp: condition: service_healthy volumes: # CRITICAL: Share uploads with webapp so worker can read files for publishing - uploads_data:/app/public/uploads - secrets_data:/secrets:ro # Why: Video uploads load 90MB+ files into memory, 512MB is too small stop_grace_period: 120s # --------------------------------------------------------------------------- # PostgreSQL Database # --------------------------------------------------------------------------- postgres: image: postgres:16.2-alpine container_name: socialiseit-postgres restart: unless-stopped networks: - default # NO external ports - internal only environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} POSTGRES_DB: ${POSTGRES_DB:-socialiseit} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}" ] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: memory: 1G cpus: '1.0' # --------------------------------------------------------------------------- # Redis for Caching and Job Queues # --------------------------------------------------------------------------- redis: image: redis:7.2-alpine container_name: socialiseit-redis restart: unless-stopped networks: - default # NO external ports - internal only # Password authentication for production security command: redis-server --requirepass ${REDIS_PASSWORD:-socialiseit_redis} environment: - REDIS_PASSWORD=${REDIS_PASSWORD:-socialiseit_redis} volumes: - redis_data:/data healthcheck: test: [ "CMD-SHELL", "redis-cli -a $$REDIS_PASSWORD ping | grep -q PONG" ] interval: 10s timeout: 5s retries: 5 start_period: 5s deploy: resources: limits: memory: 512M cpus: '0.5' # --------------------------------------------------------------------------- # MinIO S3-Compatible Object Storage # --------------------------------------------------------------------------- minio: image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z container_name: socialiseit-minio restart: unless-stopped networks: - default - proxy-net # Optional: if you want CDN access via NPM command: server /data --console-address ":9001" # NO external ports - access via NPM or internal only environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} volumes: - minio_data:/data healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ] interval: 30s timeout: 20s retries: 3 # ============================================================================= # Networks # ============================================================================= networks: default: name: socialiseit-internal proxy-net: external: true # REQUIRED: Run once on server: docker network create proxy-net # ============================================================================= # Volumes (Persistent Data) # ============================================================================= volumes: secrets_data: name: socialiseit-secrets postgres_data: name: socialiseit-postgres-data redis_data: name: socialiseit-redis-data minio_data: name: socialiseit-minio-data uploads_data: name: socialiseit-uploads-data