# VPS stack — see docker-compose.pi.yml for the separate Raspberry Pi
# storage stack. These two stacks are never deployed on the same host;
# they communicate only over WireGuard. See docs/PROJECT_PLAN.md §50.
services:
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./infrastructure/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - backend
      - frontend
    networks:
      - edge
      - internal

  frontend:
    build:
      context: .
      dockerfile: infrastructure/docker/web.Dockerfile
      args:
        VITE_API_BASE_URL: ${VITE_API_BASE_URL}
    restart: unless-stopped
    expose:
      - "80"
    networks:
      - internal

  backend:
    build:
      context: .
      dockerfile: infrastructure/docker/backend.Dockerfile
    restart: unless-stopped
    expose:
      - "8000"
    env_file:
      - .env
    volumes:
      - backend_storage:/app/storage
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - internal

  worker:
    build:
      context: .
      dockerfile: infrastructure/docker/backend.Dockerfile
    restart: unless-stopped
    command: ["php", "artisan", "queue:work", "--tries=3", "--sleep=3"]
    env_file:
      - .env
    volumes:
      - backend_storage:/app/storage
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - internal

  scheduler:
    build:
      context: .
      dockerfile: infrastructure/docker/backend.Dockerfile
    restart: unless-stopped
    command: ["php", "artisan", "schedule:work"]
    env_file:
      - .env
    volumes:
      - backend_storage:/app/storage
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - internal

  mysql:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - internal
    # Intentionally has no `ports:` mapping — never expose MySQL publicly.

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - internal
    # Intentionally has no `ports:` mapping — never expose Redis publicly.

  # Optional (§2's "Later: Prometheus, Grafana"). Never started by a
  # plain `docker compose up` — only with `--profile monitoring`. Both
  # scrape/read over the internal network only; neither is reachable
  # from caddy/edge, and /metrics itself 404s at the public edge
  # (see infrastructure/caddy/Caddyfile).
  prometheus:
    image: prom/prometheus:latest
    restart: unless-stopped
    profiles: ["monitoring"]
    volumes:
      - ./infrastructure/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    depends_on:
      - backend
    networks:
      - internal

  grafana:
    image: grafana/grafana:latest
    restart: unless-stopped
    profiles: ["monitoring"]
    environment:
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
      GF_USERS_ALLOW_SIGN_UP: "false"
    volumes:
      - ./infrastructure/monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
      - grafana_data:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - internal
    # Deliberately no public port mapping and no caddy route — reach it
    # via `ssh -L 3000:localhost:3000` port-forwarding, not the internet.
    expose:
      - "3000"

networks:
  edge:
  # Deliberately NOT `internal: true`. That flag doesn't affect whether
  # anything on this network can be reached from outside (that's
  # entirely controlled by the absence of `ports:` on mysql/redis below —
  # unaffected either way) — it instead removes the container's default
  # route, which quietly breaks every outbound connection backend/worker/
  # scheduler legitimately need to make (to the Storage Agent over
  # WireGuard, to an SMTP relay). A real disaster-recovery drill against
  # this exact file (not a simplified test copy) caught this: with
  # `internal: true` set, `ip route` inside the backend container showed
  # no default route at all, and every outbound request — even to a bare
  # IP — failed with "Network unreachable". See docs/PROJECT_PLAN.md §50.
  internal:

volumes:
  mysql_data:
  redis_data:
  backend_storage:
  caddy_data:
  caddy_config:
  prometheus_data:
  grafana_data:
