# LiteLLM proxy + Postgres. Everything reaches it through Traefik (no
# published ports); the other stacks talk to it by name on the internal
# network. Secrets and the hostname come from .env (see env.example).
name: litellm

services:
  litellm:
    # Pin a tag; main-latest has been unstable. Releases:
    # https://github.com/BerriAI/litellm/releases (the -stable suffix ended
    # around v1.83; newer releases carry plain version tags).
    image: ghcr.io/berriai/litellm:main-v1.83.14-stable
    container_name: litellm
    # For debugging from the host itself you can temporarily publish
    #   ports: ["127.0.0.1:4000:4000"]
    volumes:
      - ./data/config.yaml:/app/config.yaml:ro
    command: ["--config", "/app/config.yaml"]
    env_file:
      - .env
    environment:
      DATABASE_URL: postgresql://litellm:${POSTGRES_PASSWORD}@db:5432/litellm
      LITELLM_TELEMETRY: "False"       # no phoning home
      NO_DOCS: "True"                  # no Swagger UI on the API port
    depends_on:
      db:
        condition: service_healthy
    networks:
      internal:
        ipv4_address: 10.0.4.82        # fixed addresses are our habit, not a requirement
      traefik:
        ipv4_address: 10.0.4.36
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.litellm.rule=Host(`${LITELLM_DOMAIN}`)"
      - "traefik.http.routers.litellm.entrypoints=websecure"
      - "traefik.http.routers.litellm.tls=true"
      - "traefik.http.routers.litellm.tls.certresolver=${TRAEFIK_CERTRESOLVER:-letsencrypt}"
      - "traefik.http.services.litellm.loadbalancer.server.port=4000"
      - "traefik.http.services.litellm.loadbalancer.server.scheme=http"
      # Optional: restrict the API/UI to internal ranges. Traffic arrives via
      # the front proxy, so RemoteAddr is always 10.0.12.30; without
      # ipstrategy.depth=1 the list would be checked against the proxy's own
      # address (inside 10.0.0.0/8) and admit everyone. depth=1 takes the
      # real client from X-Forwarded-For. Commented out on our host at the
      # time of writing; enable it unless the API must be reachable from
      # outside these ranges.
      #- "traefik.http.middlewares.litellm-internal.ipallowlist.sourcerange=10.0.0.0/8,192.168.0.0/16,172.16.0.0/12"
      #- "traefik.http.middlewares.litellm-internal.ipallowlist.ipstrategy.depth=1"
      - "traefik.http.routers.litellm.middlewares=robots-tag-header@file"
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:4000/health/liveliness')"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  db:
    image: postgres:17-alpine
    container_name: litellm-db
    environment:
      POSTGRES_USER: litellm
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: litellm
    volumes:
      - ./data/pgdata:/var/lib/postgresql/data
    networks:
      internal:
        ipv4_address: 10.0.4.83
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U litellm -d litellm"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

networks:
  # A free /29 in the host's 10.0.4.0/24 Docker address plan. internal: true
  # means the database has no way out; litellm reaches the providers through
  # the traefik network. Other stacks (hermes, open-webui) join this network
  # as `litellm-internal` (external: true) to reach the proxy by name.
  internal:
    name: litellm-internal
    internal: true
    ipam:
      config:
        - subnet: 10.0.4.80/29
          gateway: 10.0.4.81
  traefik:
    external: true
