diff --git a/.env.swarm.example b/.env.swarm.example index a1a37ca..efa9d8a 100644 --- a/.env.swarm.example +++ b/.env.swarm.example @@ -130,6 +130,14 @@ GITEA_REPO_NAME=stack GITEA_WEBHOOK_SECRET=REPLACE_WITH_RANDOM_WEBHOOK_SECRET COORDINATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS +# ====================== +# Coordinator Service +# ====================== +ANTHROPIC_API_KEY=REPLACE_WITH_ANTHROPIC_API_KEY +COORDINATOR_POLL_INTERVAL=5.0 +COORDINATOR_MAX_CONCURRENT_AGENTS=10 +COORDINATOR_ENABLED=true + # ====================== # Rate Limiting # ====================== diff --git a/.gitignore b/.gitignore index 85daeb0..1ce13dc 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ Thumbs.db .env.test.local .env.production.local .env.bak.* +*.bak # Credentials (never commit) .admin-credentials diff --git a/docker-compose.swarm.portainer.yml b/docker-compose.swarm.portainer.yml new file mode 100644 index 0000000..84a2695 --- /dev/null +++ b/docker-compose.swarm.portainer.yml @@ -0,0 +1,415 @@ +# ============================================== +# Mosaic Stack - Docker Swarm Deployment +# ============================================== +# +# IMPORTANT: Docker Swarm does NOT support docker-compose profiles +# To disable services (e.g., for external alternatives), manually comment them out +# +# Current Configuration: +# - PostgreSQL: ENABLED (internal) +# - Valkey: ENABLED (internal) +# - Coordinator: ENABLED (internal) +# - OpenBao: DISABLED (must use standalone - see docker-compose.openbao.yml) +# - Authentik: DISABLED (commented out - using external OIDC) +# - Ollama: DISABLED (commented out - using external Ollama) +# +# For detailed deployment instructions, see: +# docs/SWARM-DEPLOYMENT.md +# +# Quick Start: +# 1. cp .env.swarm.example .env +# 2. nano .env # Configure environment +# 3. ./scripts/deploy-swarm.sh mosaic +# 4. Initialize OpenBao manually (see docs/SWARM-DEPLOYMENT.md) +# +# ============================================== + +services: + # ====================== + # PostgreSQL Database + # ====================== + postgres: + image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest} + environment: + POSTGRES_USER: ${POSTGRES_USER:-mosaic} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_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 + # Note: init-scripts bind mount removed for Portainer compatibility + # Init scripts are baked into the postgres image at build time + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - internal + deploy: + restart_policy: + condition: on-failure + + # ====================== + # Valkey Cache + # ====================== + valkey: + image: valkey/valkey:8-alpine + 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: + - internal + deploy: + restart_policy: + condition: on-failure + + # ====================== + # OpenBao Secrets Vault - COMMENTED OUT + # ====================== + # IMPORTANT: OpenBao CANNOT run in swarm mode due to port binding conflicts. + # Deploy OpenBao as a standalone container instead: + # docker compose -f docker-compose.openbao.yml up -d + # + # Alternative: Use external HashiCorp Vault or managed secrets service + # + # openbao: + # image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest} + # environment: + # OPENBAO_ADDR: ${OPENBAO_ADDR:-http://0.0.0.0:8200} + # OPENBAO_DEV_ROOT_TOKEN_ID: ${OPENBAO_DEV_ROOT_TOKEN_ID:-root} + # volumes: + # - openbao_data:/openbao/data + # - openbao_logs:/openbao/logs + # - openbao_init:/openbao/init + # cap_add: + # - IPC_LOCK + # healthcheck: + # test: + # ["CMD", "wget", "--spider", "--quiet", "http://localhost:8200/v1/sys/health?standbyok=true"] + # interval: 10s + # timeout: 5s + # retries: 5 + # start_period: 30s + # networks: + # - internal + # deploy: + # restart_policy: + # condition: on-failure + + # ====================== + # Authentik - COMMENTED OUT (Using External Authentik) + # ====================== + # Uncomment these services if you want to run Authentik internally + # For external Authentik, configure OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET in .env + # + # authentik-postgres: + # image: postgres:17-alpine + # environment: + # POSTGRES_USER: ${AUTHENTIK_POSTGRES_USER:-authentik} + # POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password} + # POSTGRES_DB: ${AUTHENTIK_POSTGRES_DB:-authentik} + # volumes: + # - authentik_postgres_data:/var/lib/postgresql/data + # healthcheck: + # test: ["CMD-SHELL", "pg_isready -U ${AUTHENTIK_POSTGRES_USER:-authentik}"] + # interval: 10s + # timeout: 5s + # retries: 5 + # start_period: 20s + # networks: + # - internal + # deploy: + # restart_policy: + # condition: on-failure + # + # authentik-redis: + # image: valkey/valkey:8-alpine + # command: valkey-server --save 60 1 --loglevel warning + # volumes: + # - authentik_redis_data:/data + # healthcheck: + # test: ["CMD", "valkey-cli", "ping"] + # interval: 10s + # timeout: 5s + # retries: 5 + # start_period: 10s + # networks: + # - internal + # deploy: + # restart_policy: + # condition: on-failure + # + # authentik-server: + # image: ghcr.io/goauthentik/server:2024.12.1 + # command: server + # environment: + # AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret} + # AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false} + # AUTHENTIK_POSTGRESQL__HOST: authentik-postgres + # AUTHENTIK_POSTGRESQL__PORT: 5432 + # AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik} + # AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik} + # AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password} + # AUTHENTIK_REDIS__HOST: authentik-redis + # AUTHENTIK_REDIS__PORT: 6379 + # AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin} + # AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@localhost} + # AUTHENTIK_COOKIE_DOMAIN: ${AUTHENTIK_COOKIE_DOMAIN:-.mosaicstack.dev} + # volumes: + # - authentik_media:/media + # - authentik_templates:/templates + # healthcheck: + # test: + # [ + # "CMD", + # "wget", + # "--no-verbose", + # "--tries=1", + # "--spider", + # "http://localhost:9000/-/health/live/", + # ] + # interval: 30s + # timeout: 10s + # retries: 3 + # start_period: 90s + # networks: + # - internal + # - traefik-public + # deploy: + # restart_policy: + # condition: on-failure + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.mosaic-auth.rule=Host(`${MOSAIC_AUTH_DOMAIN:-auth.mosaicstack.dev}`)" + # - "traefik.http.routers.mosaic-auth.entrypoints=web" + # - "traefik.http.services.mosaic-auth.loadbalancer.server.port=9000" + # + # authentik-worker: + # image: ghcr.io/goauthentik/server:2024.12.1 + # command: worker + # environment: + # AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret} + # AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false} + # AUTHENTIK_POSTGRESQL__HOST: authentik-postgres + # AUTHENTIK_POSTGRESQL__PORT: 5432 + # AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik} + # AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik} + # AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password} + # AUTHENTIK_REDIS__HOST: authentik-redis + # AUTHENTIK_REDIS__PORT: 6379 + # volumes: + # - authentik_media:/media + # - authentik_certs:/certs + # - authentik_templates:/templates + # networks: + # - internal + # deploy: + # restart_policy: + # condition: on-failure + + # ====================== + # Ollama (Optional AI Service) + # ====================== + # ollama: + # image: ollama/ollama:latest + # volumes: + # - ollama_data:/root/.ollama + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"] + # interval: 30s + # timeout: 10s + # retries: 3 + # start_period: 60s + # networks: + # - internal + # deploy: + # restart_policy: + # condition: on-failure + + # ====================== + # Mosaic Coordinator + # ====================== + coordinator: + image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest} + environment: + GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET} + GITEA_URL: ${GITEA_URL:-https://git.mosaicstack.dev} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + LOG_LEVEL: ${LOG_LEVEL:-info} + HOST: 0.0.0.0 + PORT: 8000 + COORDINATOR_POLL_INTERVAL: ${COORDINATOR_POLL_INTERVAL:-5.0} + COORDINATOR_MAX_CONCURRENT_AGENTS: ${COORDINATOR_MAX_CONCURRENT_AGENTS:-10} + COORDINATOR_ENABLED: ${COORDINATOR_ENABLED:-true} + healthcheck: + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')", + ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + networks: + - internal + deploy: + restart_policy: + condition: on-failure + + # ====================== + # Mosaic API + # ====================== + api: + image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest} + environment: + NODE_ENV: production + PORT: ${API_PORT:-3001} + API_HOST: ${API_HOST:-0.0.0.0} + DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_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:-http://localhost:3001/auth/callback} + JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret} + JWT_EXPIRATION: ${JWT_EXPIRATION:-24h} + OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} + OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + 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: + - internal + - traefik-public + deploy: + restart_policy: + condition: on-failure + labels: + - "traefik.enable=true" + - "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)" + - "traefik.http.routers.mosaic-api.entrypoints=web" + - "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}" + + # ====================== + # Mosaic Orchestrator + # ====================== + orchestrator: + image: git.mosaicstack.dev/mosaic/stack-orchestrator:${IMAGE_TAG:-latest} + user: "1000:1000" + environment: + NODE_ENV: production + ORCHESTRATOR_PORT: 3001 + VALKEY_URL: redis://valkey:6379 + CLAUDE_API_KEY: ${CLAUDE_API_KEY} + DOCKER_SOCKET: /var/run/docker.sock + GIT_USER_NAME: "Mosaic Orchestrator" + GIT_USER_EMAIL: "orchestrator@mosaicstack.dev" + KILLSWITCH_ENABLED: "true" + SANDBOX_ENABLED: "true" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - orchestrator_workspace:/workspace + healthcheck: + test: + ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - internal + # Note: security_opt not supported in swarm mode + # Security hardening done via cap_drop/cap_add + cap_drop: + - ALL + cap_add: + - NET_BIND_SERVICE + tmpfs: + - /tmp:noexec,nosuid,size=100m + deploy: + restart_policy: + condition: on-failure + + # ====================== + # Mosaic Web + # ====================== + web: + image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest} + environment: + NODE_ENV: production + PORT: ${WEB_PORT:-3000} + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001} + 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: + - traefik-public + deploy: + restart_policy: + condition: on-failure + labels: + - "traefik.enable=true" + - "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.mosaicstack.dev}`)" + - "traefik.http.routers.mosaic-web.entrypoints=web" + - "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}" + +# ====================== +# Volumes +# ====================== +volumes: + postgres_data: + valkey_data: + # OpenBao volumes - commented out (using standalone deployment) + # openbao_data: + # openbao_logs: + # openbao_init: + # Authentik volumes - commented out (using external Authentik) + # authentik_postgres_data: + # authentik_redis_data: + # authentik_media: + # authentik_certs: + # authentik_templates: + # Ollama volume - commented out (using external Ollama) + # ollama_data: + orchestrator_workspace: + +# ====================== +# Networks +# ====================== +networks: + internal: + driver: overlay + traefik-public: + external: true diff --git a/docker-compose.swarm.yml b/docker-compose.swarm.yml index c2059f7..4a9a348 100644 --- a/docker-compose.swarm.yml +++ b/docker-compose.swarm.yml @@ -8,6 +8,7 @@ # Current Configuration: # - PostgreSQL: ENABLED (internal) # - Valkey: ENABLED (internal) +# - Coordinator: ENABLED (internal) # - OpenBao: DISABLED (must use standalone - see docker-compose.openbao.yml) # - Authentik: DISABLED (commented out - using external OIDC) # - Ollama: DISABLED (commented out - using external Ollama) @@ -230,17 +231,51 @@ services: # ====================== # Ollama (Optional AI Service) # ====================== - ollama: - image: ollama/ollama:latest + # ollama: + # image: ollama/ollama:latest + # env_file: .env + # volumes: + # - ollama_data:/root/.ollama + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"] + # interval: 30s + # timeout: 10s + # retries: 3 + # start_period: 60s + # networks: + # - internal + # deploy: + # restart_policy: + # condition: on-failure + + # ====================== + # Mosaic Coordinator + # ====================== + coordinator: + image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest} env_file: .env - volumes: - - ollama_data:/root/.ollama + environment: + GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET} + GITEA_URL: ${GITEA_URL:-https://git.mosaicstack.dev} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + LOG_LEVEL: ${LOG_LEVEL:-info} + HOST: 0.0.0.0 + PORT: 8000 + COORDINATOR_POLL_INTERVAL: ${COORDINATOR_POLL_INTERVAL:-5.0} + COORDINATOR_MAX_CONCURRENT_AGENTS: ${COORDINATOR_MAX_CONCURRENT_AGENTS:-10} + COORDINATOR_ENABLED: ${COORDINATOR_ENABLED:-true} healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"] + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')", + ] interval: 30s timeout: 10s retries: 3 - start_period: 60s + start_period: 5s networks: - internal deploy: diff --git a/docker-compose.override.yml.example b/docker/docker-compose.override.yml.example similarity index 100% rename from docker-compose.override.yml.example rename to docker/docker-compose.override.yml.example diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml new file mode 100644 index 0000000..dd346a9 --- /dev/null +++ b/docker/docker-compose.prod.yml @@ -0,0 +1,179 @@ +# Production Docker Compose - Uses pre-built images from Gitea Packages +# +# Prerequisites: +# - Images built and pushed to git.mosaicstack.dev/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: git.mosaicstack.dev/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: git.mosaicstack.dev/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: git.mosaicstack.dev/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