# Arche Production Compose — generated by deploy.sh
# Mode: {{ deploy_mode }}
# Exposure: {{ exposure_mode }}
# Domain: {{ domain }}

name: arche

services:
  traefik:
    image: docker.io/traefik:v3.6.7
    restart: "on-failure:5"
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.endpoint=tcp://docker-socket-proxy:2375
{% if deploy_mode == 'remote' and exposure_mode == 'direct' %}
      # HTTPS entrypoint
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entryPoint.to=websecure
      - --entrypoints.web.http.redirections.entryPoint.scheme=https
      - --entrypoints.websecure.address=:443
      # ACME HTTP challenge (no DNS provider needed)
      - --certificatesResolvers.letsencrypt.acme.email={{ acme_email }}
      - --certificatesResolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web
{% elif deploy_mode == 'remote' and exposure_mode == 'cloudflare-tunnel' %}
      # HTTP-only behind Cloudflare Tunnel
      - --entrypoints.web.address=:80
{% else %}
      # HTTP-only (local-dev mode)
      - --entrypoints.web.address=:80
{% endif %}
{% if deploy_mode == 'local-dev' %}
      - --api.dashboard=true
      - --api.insecure=true
{% else %}
      - --api.dashboard=false
{% endif %}
      - --log.level=INFO
{% if deploy_mode == 'remote' and exposure_mode == 'direct' %}
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - traefik_letsencrypt:/letsencrypt
{% elif deploy_mode == 'local-dev' %}
    ports:
      - "8080:80"
      - "8081:8080"
{% endif %}
    networks:
      - default
      - arche-internal
    depends_on:
      - docker-socket-proxy

{% if deploy_mode == 'remote' and exposure_mode == 'cloudflare-tunnel' %}
  cloudflared:
    image: {{ cloudflared_image }}
    restart: "on-failure:5"
    command:
      - tunnel
      - --no-autoupdate
      - run
    environment:
      TUNNEL_TOKEN: "${CLOUDFLARED_TUNNEL_TOKEN}"
    networks:
      - default
    depends_on:
      - traefik
{% endif %}

  docker-socket-proxy:
    image: ghcr.io/tecnativa/docker-socket-proxy:latest
    restart: "on-failure:5"
{% if deploy_mode == 'local-dev' %}
    user: root
    security_opt:
      - label=disable
{% endif %}
    environment:
      CONTAINERS: 1
      NETWORKS: 1
      IMAGES: 1
      INFO: 1
      POST: 1
      VOLUMES: 1
    volumes:
      - {{ podman_socket_path }}:/var/run/docker.sock:ro
    networks:
      - arche-internal
    healthcheck:
      test: ["CMD-SHELL", "test -S /var/run/docker.sock"]
      interval: 10s
      timeout: 5s
      retries: 5

  postgres:
    image: docker.io/postgres:16
    restart: "on-failure:5"
    environment:
      POSTGRES_DB: arche
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
{% if deploy_mode == 'local-dev' %}
    command:
      - sh
      - -lc
      - |
        set -eu
        export PATH="/usr/lib/postgresql/$$PG_MAJOR/bin:$$PATH"
        pid=''
        cleanup() {
          if [ -n "$$pid" ]; then
            kill -TERM "$$pid" 2>/dev/null || true
            wait "$$pid" || true
          fi
        }
        trap cleanup INT TERM HUP
        docker-entrypoint.sh postgres &
        pid="$$!"
        until gosu postgres pg_isready -U "$$POSTGRES_USER" -d postgres >/dev/null 2>&1; do
          sleep 1
        done
        if ! gosu postgres psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = '$$POSTGRES_DB';" | grep -q 1; then
          gosu postgres createdb -U "$$POSTGRES_USER" "$$POSTGRES_DB"
        fi
        gosu postgres psql -v ON_ERROR_STOP=1 -U "$$POSTGRES_USER" -d postgres -c "ALTER USER \"$$POSTGRES_USER\" PASSWORD '$$POSTGRES_PASSWORD';"
        wait "$$pid"
    ports:
      - "5432:5432"
{% endif %}
    volumes:
      - arche_postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

{% if deploy_mode == 'remote' %}
  reaper:
    image: {{ web_image }}
    restart: "on-failure:5"
    command:
      - pnpm
      - run
      - start:reaper
    env_file:
      - {{ env_file_name | default('.env') }}
    # Reaper only reconciles database state with container lifecycle via Docker.
    # It does not read KB config/content or write user workspace data.
    networks:
      - default
      - arche-internal
    depends_on:
      postgres:
        condition: service_healthy
      docker-socket-proxy:
        condition: service_healthy
    healthcheck:
      disable: true

  flows:
    image: {{ web_image }}
    restart: "unless-stopped"
    command:
      - pnpm
      - run
      - start:flows
    env_file:
      - {{ env_file_name | default('.env') }}
    networks:
      - default
      - arche-internal
    volumes:
      - {{ users_path | default('/opt/arche/users') }}:{{ users_path | default('/opt/arche/users') }}
      - {{ kb_content_host_path | default('/opt/arche/kb-content') }}:/kb-content
      - {{ kb_config_host_path | default('/opt/arche/kb-config') }}:/kb-config
    depends_on:
      postgres:
        condition: service_healthy
      docker-socket-proxy:
        condition: service_healthy
    healthcheck:
      disable: true
{% endif %}

{% if deploy_mode == 'local-dev' %}
  # In local-dev mode, web runs in compose with source mounts and
  # Turbopack for fast local iteration. If macOS/Podman bind mounts miss a
  # file-watch event, scripts/reload-web-dev.sh restarts only the Next dev server.
  # Runtime caches stay outside
  # the source bind mount so file watching stays focused on source files
  # and Next's route compilation cache avoids macOS bind-mount writes.
  # In remote mode, web is managed outside compose via podman run for
  # true blue-green zero-downtime deployments.
  web:
    image: node:24.14.0-bookworm-slim
    working_dir: /app
    restart: "unless-stopped"
    env_file:
      - {{ env_file_name | default('.env') }}
    ports:
      - "3000:3000"
    environment:
      ARCHE_GATEWAY_TOKEN_SECRET: "${ARCHE_GATEWAY_TOKEN_SECRET}"
      ARCHE_GATEWAY_TOKEN_TTL_SECONDS: "${ARCHE_GATEWAY_TOKEN_TTL_SECONDS}"
      ARCHE_GATEWAY_BASE_URL: "${ARCHE_GATEWAY_BASE_URL}"
      NEXT_TELEMETRY_DISABLED: "1"
      NODE_COMPILE_CACHE: "/tmp/node-compile-cache"
      PNPM_STORE_DIR: "/pnpm/store"
    networks:
      - default
      - arche-internal
    depends_on:
      postgres:
        condition: service_healthy
    labels:
      - traefik.enable=true
      - traefik.http.routers.arche-base.rule=Host(`{{ domain }}`)
      - traefik.http.routers.arche-base.entrypoints=web
      - traefik.http.routers.arche-base.service=arche-web
      - traefik.http.services.arche-web.loadbalancer.server.port=3000
    volumes:
      - {{ repo_root }}/apps/web:/app
      - arche_web_next:/app/.next
      - arche_web_node_modules:/app/node_modules
      - arche_pnpm_store:/pnpm/store
      - {{ users_path | default('/opt/arche/users') }}:{{ users_path | default('/opt/arche/users') }}
      - {{ kb_content_host_path | default('/opt/arche/kb-content') }}:/kb-content
      - {{ kb_config_host_path | default('/opt/arche/kb-config') }}:/kb-config
    command: sh scripts/dev-server.sh
{% endif %}

networks:
  default:
  arche-internal:
    name: arche-internal
    external: true

volumes:
  arche_postgres:
{% if deploy_mode == 'remote' and exposure_mode == 'direct' %}
  traefik_letsencrypt:
{% endif %}
{% if deploy_mode == 'local-dev' %}
  arche_web_node_modules:
  arche_web_next:
  arche_pnpm_store:
{% endif %}
