# Production Docker Compose - Uses pre-built images from Harbor # # Prerequisites: # - Images built and pushed to reg.diversecanvas.com/mosaic/* # - .env file configured with production values # # Usage: # docker compose -f docker-compose.prod.yml up -d # # For Portainer: # - Stack → Add Stack → Repository # - Compose file: docker-compose.prod.yml services: # ====================== # PostgreSQL Database # ====================== postgres: image: reg.diversecanvas.com/mosaic/postgres:latest container_name: mosaic-postgres restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-mosaic} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-mosaic} POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB} POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB} POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"] interval: 10s timeout: 5s retries: 5 start_period: 30s networks: - mosaic-internal labels: - "com.mosaic.service=database" - "com.mosaic.description=PostgreSQL 17 with pgvector" # ====================== # Valkey Cache # ====================== valkey: image: valkey/valkey:8-alpine container_name: mosaic-valkey restart: unless-stopped command: - valkey-server - --maxmemory ${VALKEY_MAXMEMORY:-256mb} - --maxmemory-policy allkeys-lru - --appendonly yes volumes: - valkey_data:/data healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s networks: - mosaic-internal labels: - "com.mosaic.service=cache" - "com.mosaic.description=Valkey Redis-compatible cache" # ====================== # Mosaic API # ====================== api: image: reg.diversecanvas.com/mosaic/api:latest container_name: mosaic-api restart: unless-stopped environment: NODE_ENV: production PORT: ${API_PORT:-3001} API_HOST: ${API_HOST:-0.0.0.0} DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-mosaic} VALKEY_URL: redis://valkey:6379 OIDC_ISSUER: ${OIDC_ISSUER} OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI} JWT_SECRET: ${JWT_SECRET} JWT_EXPIRATION: ${JWT_EXPIRATION:-24h} OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} ports: - "${API_PORT:-3001}:${API_PORT:-3001}" depends_on: postgres: condition: service_healthy valkey: condition: service_healthy healthcheck: test: [ "CMD-SHELL", 'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"', ] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - mosaic-internal - mosaic-public labels: - "com.mosaic.service=api" - "com.mosaic.description=Mosaic NestJS API" - "traefik.enable=${TRAEFIK_ENABLE:-false}" - "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)" - "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}" - "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}" - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" # ====================== # Mosaic Web # ====================== web: image: reg.diversecanvas.com/mosaic/web:latest container_name: mosaic-web restart: unless-stopped environment: NODE_ENV: production PORT: ${WEB_PORT:-3000} NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.mosaicstack.dev} ports: - "${WEB_PORT:-3000}:${WEB_PORT:-3000}" depends_on: api: condition: service_healthy healthcheck: test: [ "CMD-SHELL", 'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"', ] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - mosaic-public labels: - "com.mosaic.service=web" - "com.mosaic.description=Mosaic Next.js Web App" - "traefik.enable=${TRAEFIK_ENABLE:-false}" - "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-app.mosaicstack.dev}`)" - "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}" - "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}" - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" # ====================== # Volumes # ====================== volumes: postgres_data: name: mosaic-postgres-data driver: local valkey_data: name: mosaic-valkey-data driver: local # ====================== # Networks # ====================== networks: mosaic-internal: name: mosaic-internal driver: bridge mosaic-public: name: mosaic-public driver: bridge