# Environment Variable Loading Order (first → last, later overrides earlier):
# 1. backend/.env.default - Default values for all settings
# 2. backend/.env - User's custom configuration (if exists)
# 3. environment key - Docker-specific overrides defined below
# 4. Shell environment - Variables exported before running docker compose
# 5. CLI arguments - docker compose run -e VAR=value

# Common backend environment - Docker service names
x-backend-env:
  &backend-env # Docker internal service hostnames (override localhost defaults)
  PYRO_HOST: "0.0.0.0"
  AGENTSERVER_HOST: rest_server
  SCHEDULER_HOST: scheduler_server
  DATABASEMANAGER_HOST: database_manager
  EXECUTIONMANAGER_HOST: executor
  NOTIFICATIONMANAGER_HOST: notification_server
  CLAMAV_SERVICE_HOST: clamav
  DB_HOST: db
  REDIS_HOST: redis-0
  REDIS_PORT: "17000"
  # The 3-container local cluster announces distinct compose hostnames per
  # shard (redis-0/1/2), each resolvable via Docker DNS inside the compose
  # network. Bypass the HOST-pinning address_remap so CLUSTER SLOTS uses the
  # announced addresses directly — see ``backend.data.redis_client``.
  REDIS_USE_ANNOUNCED_ADDRESS: "true"
  RABBITMQ_HOST: rabbitmq
  GRAPHITI_FALKORDB_HOST: falkordb
  GRAPHITI_FALKORDB_PORT: "6379"
  # Override Supabase URL for Docker network
  SUPABASE_URL: http://kong:8000
  # Database connection string for Docker network
  # This cannot be constructed like in .env because we cannot interpolate values set here (DB_HOST)
  DATABASE_URL: postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=platform
  DIRECT_URL: postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=platform

# Common env_file configuration for backend services
x-backend-env-files: &backend-env-files
  env_file:
    - backend/.env.default # Base defaults (always exists)
    - path: backend/.env # User overrides (optional)
      required: false

# Shared base for the three Redis Cluster shards + one-shot init sidecar.
# The anchor absorbs image + network so each service body collapses to its
# hostname + command + (for the seed) volume + healthcheck.
x-redis-node: &redis-node
  image: redis:7
  networks:
    - app-network

services:
  migrate:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: migrate
    command:
      [
        "sh",
        "-c",
        "prisma generate && python3 scripts/gen_prisma_types_stub.py && prisma migrate deploy",
      ]
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/migrations
          action: rebuild
    depends_on:
      db:
        condition: service_healthy
    <<: *backend-env-files
    environment:
      <<: *backend-env
    networks:
      - app-network
    restart: on-failure
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "prisma migrate status | grep -q 'No pending migrations' || exit 1",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s

  # Three-container 3-master Redis Cluster for local dev. Stock ``redis:7``
  # on each shard runs its default entrypoint; the one-shot ``redis-init``
  # sidecar runs ``redis-cli --cluster create`` once and exits. No volumes
  # — local dev treats the cluster as cache-only, so every ``docker compose
  # up`` starts fresh. (Persisting only the seed volume across restarts is
  # a trap: the other shards come back with new IDs and the seed's
  # ``nodes.conf`` pins stale peers that cluster gossip can't heal.) Each
  # shard announces its own compose hostname via
  # ``--cluster-announce-hostname`` so both in-compose clients (Docker DNS)
  # and host-native clients (handled by the backend's ``address_remap``)
  # receive a consistent address on CLUSTER SLOTS. Ports 17000/17001/17002
  # sit well clear of FalkorDB on host 6380.
  redis-0:
    <<: *redis-node
    hostname: redis-0
    command: redis-server --port 17000 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --cluster-require-full-coverage no --cluster-announce-hostname redis-0 --cluster-announce-port 17000 --cluster-announce-bus-port 27000 --cluster-preferred-endpoint-type hostname
    ports:
      - "17000:17000"
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "redis-cli -p 17000 ping && redis-cli -p 17000 cluster info | grep -q 'cluster_state:ok'",
        ]
      interval: 10s
      timeout: 5s
      retries: 30
  redis-1:
    <<: *redis-node
    hostname: redis-1
    command: redis-server --port 17001 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --cluster-require-full-coverage no --cluster-announce-hostname redis-1 --cluster-announce-port 17001 --cluster-announce-bus-port 27001 --cluster-preferred-endpoint-type hostname
    ports:
      - "17001:17001"
  redis-2:
    <<: *redis-node
    hostname: redis-2
    command: redis-server --port 17002 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --cluster-require-full-coverage no --cluster-announce-hostname redis-2 --cluster-announce-port 17002 --cluster-announce-bus-port 27002 --cluster-preferred-endpoint-type hostname
    ports:
      - "17002:17002"
  # The ``|| cluster create`` short-circuit makes this idempotent within the
  # lifetime of a single compose project: a re-``up`` without ``down`` leaves
  # the already-formed cluster untouched. A ``down`` + ``up`` wipes every
  # shard (no volumes) and re-creates from scratch.
  redis-init:
    <<: *redis-node
    depends_on: [redis-0, redis-1, redis-2]
    restart: "no"
    command: >-
      sh -c "redis-cli -h redis-0 -p 17000 cluster info 2>/dev/null | grep -q cluster_state:ok ||
      redis-cli --cluster create redis-0:17000 redis-1:17001 redis-2:17002 --cluster-replicas 0 --cluster-yes"
  falkordb:
    image: falkordb/falkordb:latest
    ports:
      - "6380:6379" # FalkorDB Redis protocol (6380 to avoid clash with Redis on 6379)
      - "3001:3000" # FalkorDB web UI
    <<: *backend-env-files
    # `:?` exits the container if the var is unset/empty, surfacing a
    # missing-config error instead of silent `--requirepass ""` auth.
    entrypoint:
      - /bin/sh
      - -c
      - >-
        export REDIS_ARGS="--requirepass $${GRAPHITI_FALKORDB_PASSWORD:?GRAPHITI_FALKORDB_PASSWORD must be set}"
        && exec /var/lib/falkordb/bin/run.sh "$$@"
      - --
    volumes:
      - falkordb_data:/data
    networks:
      - app-network
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'redis-cli -p 6379 -a "$${GRAPHITI_FALKORDB_PASSWORD:-}" --no-auth-warning ping | grep -q PONG',
        ]
      interval: 10s
      timeout: 5s
      retries: 5

  rabbitmq:
    image: rabbitmq:4.1.4
    container_name: rabbitmq
    healthcheck:
      test: rabbitmq-diagnostics -q ping
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 10s
    <<: *backend-env-files
    environment:
      <<: *backend-env
    ports:
      - "5672:5672"

  rest_server:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: server
    command: ["rest"] # points to entry in [tool.poetry.scripts] in pyproject.toml
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/
          action: rebuild
    depends_on:
      redis-0:
        condition: service_healthy
      db:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
      rabbitmq:
        condition: service_healthy
    <<: *backend-env-files
    environment:
      <<: *backend-env
    ports:
      - "8006:8006"
    volumes:
      - workspace-data:/app/autogpt_platform/backend/workspaces
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  executor:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: server
    command: ["executor"] # points to entry in [tool.poetry.scripts] in pyproject.toml
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/
          action: rebuild
    depends_on:
      redis-0:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      db:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
      database_manager:
        condition: service_started
    <<: *backend-env-files
    environment:
      <<: *backend-env
    ports:
      - "8002:8002"
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  copilot_executor:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: server
    command: ["python", "-u", "-m", "backend.copilot.executor"]
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/
          action: rebuild
    depends_on:
      redis-0:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      db:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
      database_manager:
        condition: service_started
    <<: *backend-env-files
    environment:
      <<: *backend-env
      PYTHONUNBUFFERED: "1"
    ports:
      - "8008:8008"
    volumes:
      - workspace-data:/app/autogpt_platform/backend/workspaces
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  websocket_server:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: server
    command: ["ws"] # points to entry in [tool.poetry.scripts] in pyproject.toml
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/
          action: rebuild
    depends_on:
      db:
        condition: service_healthy
      redis-0:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
      database_manager:
        condition: service_started
    <<: *backend-env-files
    environment:
      <<: *backend-env
    ports:
      - "8001:8001"
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  database_manager:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: server
    command: ["db"] # points to entry in [tool.poetry.scripts] in pyproject.toml
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/
          action: rebuild
    depends_on:
      db:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
    <<: *backend-env-files
    environment:
      <<: *backend-env
    ports:
      - "8005:8005"
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  scheduler_server:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: server
    command: ["scheduler"] # points to entry in [tool.poetry.scripts] in pyproject.toml
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/
          action: rebuild
    depends_on:
      db:
        condition: service_healthy
      redis-0:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
      database_manager:
        condition: service_started
    <<: *backend-env-files
    environment:
      <<: *backend-env
    ports:
      - "8003:8003"
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  notification_server:
    build:
      context: ../
      dockerfile: autogpt_platform/backend/Dockerfile
      target: server
    command: ["notification"] # points to entry in [tool.poetry.scripts] in pyproject.toml
    develop:
      watch:
        - path: ./
          target: autogpt_platform/backend/
          action: rebuild
    depends_on:
      db:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
      database_manager:
        condition: service_started
    <<: *backend-env-files
    environment:
      <<: *backend-env
    ports:
      - "8007:8007"
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
  frontend:
    build:
      context: ../
      dockerfile: autogpt_platform/frontend/Dockerfile
      target: prod
      args:
        NEXT_PUBLIC_PW_TEST: ${NEXT_PUBLIC_PW_TEST:-false}
    depends_on:
      db:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
    ports:
      - "3000:3000"
    networks:
      - app-network
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    # Load environment variables in order (later overrides earlier)
    env_file:
      - path: ./frontend/.env.default # Base defaults (always exists)
      - path: ./frontend/.env # User overrides (optional)
        required: false
    environment:
      # Server-side environment variables (Docker service names)
      # These override the localhost URLs from env files when running in Docker
      AUTH_CALLBACK_URL: http://rest_server:8006/auth/callback
      SUPABASE_URL: http://kong:8000
      AGPT_SERVER_URL: http://rest_server:8006/api
      AGPT_WS_SERVER_URL: ws://websocket_server:8001/ws
volumes:
  workspace-data:
  falkordb_data:

networks:
  app-network:
    driver: bridge
