From bbb3eaaa071f82599c12511d3c07983cea0ec63e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 23 May 2026 22:20:05 +0000 Subject: [PATCH] feat(docker): add backend service to docker-compose matching helm chart (LIT-2815) Add the `backend` service (port 4001, backend/Dockerfile) to docker-compose.yml to mirror the three-service architecture in helm/litellm: gateway (4000), backend (4001), ui (3000). Update tests to cover the new service. https://claude.ai/code/session_01JVLLUH66aUXF9kxoHcYxWu --- docker-compose.yml | 24 +++++++++++ tests/test_litellm/test_docker_compose.py | 49 ++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 09e8f1bc944..51821e2e4f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,30 @@ services: retries: 3 # Retry up to 3 times if health check fails start_period: 40s # Wait 40 seconds after container start before beginning health checks + backend: + build: + context: . + dockerfile: backend/Dockerfile + image: docker.litellm.ai/berriai/litellm-backend:main-stable + ports: + - "4001:4001" # Map the container port to the host, change the host port if necessary + environment: + DATABASE_URL: "postgresql://llmproxy:dbpassword9090@db:5432/litellm" + # DATABASE_URL_READ_REPLICA: "postgresql://llmproxy:dbpassword9090@db-reader:5432/litellm" + STORE_MODEL_IN_DB: "True" # allows adding models to proxy via UI + env_file: + - .env # Load local .env file + depends_on: + - db # Indicates that this service depends on the 'db' service, ensuring 'db' starts first + healthcheck: + test: + - CMD-SHELL + - python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:4001/health/liveliness')" + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + db: image: postgres:16 restart: always diff --git a/tests/test_litellm/test_docker_compose.py b/tests/test_litellm/test_docker_compose.py index 0de16a271be..dfccd2e3158 100644 --- a/tests/test_litellm/test_docker_compose.py +++ b/tests/test_litellm/test_docker_compose.py @@ -1,5 +1,5 @@ """ -Static checks on docker-compose.yml to verify the UI/gateway split. +Static checks on docker-compose.yml to verify the UI/backend/gateway split. Test matrix ----------- @@ -8,16 +8,23 @@ Test matrix |----------------------------------------------------|-------------------------------------------| | `ui` service exists | key present in services dict | | `gateway` service exists | key present in services dict | +| `backend` service exists | key present in services dict | | Legacy `litellm` service is gone | key absent from services dict | | `ui` builds from `ui/Dockerfile` | build.dockerfile == "ui/Dockerfile" | | `gateway` builds from `gateway/Dockerfile` | build.dockerfile == "gateway/Dockerfile" | +| `backend` builds from `backend/Dockerfile` | build.dockerfile == "backend/Dockerfile" | | `ui` exposes port 3000 | "3000:3000" in ports list | | `gateway` exposes port 4000 | "4000:4000" in ports list | +| `backend` exposes port 4001 | "4001:4001" in ports list | | `gateway` has DATABASE_URL env var | key present in environment dict | | `gateway` has STORE_MODEL_IN_DB env var | key present in environment dict | +| `backend` has DATABASE_URL env var | key present in environment dict | +| `backend` has STORE_MODEL_IN_DB env var | key present in environment dict | | `gateway` health check is configured | healthcheck.test is non-empty | +| `backend` health check is configured | healthcheck.test is non-empty | | `ui` health check is configured | healthcheck.test is non-empty | | `gateway` depends on `db` | "db" in depends_on | +| `backend` depends on `db` | "db" in depends_on | | `db` service exists | key present in services dict | | `db` health check is configured | healthcheck.test is non-empty | | `prometheus` service exists | key present in services dict | @@ -61,6 +68,10 @@ def test_gateway_service_exists(services): assert "gateway" in services, "'gateway' service missing from docker-compose.yml" +def test_backend_service_exists(services): + assert "backend" in services, "'backend' service missing from docker-compose.yml" + + def test_legacy_litellm_service_removed(services): assert "litellm" not in services, ( "Monolithic 'litellm' service should be removed after UI/gateway split" @@ -86,6 +97,13 @@ def test_gateway_builds_from_gateway_dockerfile(services): ) +def test_backend_builds_from_backend_dockerfile(services): + build = services["backend"].get("build", {}) + assert build.get("dockerfile") == "backend/Dockerfile", ( + f"backend service should build from backend/Dockerfile, got {build.get('dockerfile')}" + ) + + # --------------------------------------------------------------------------- # Port exposure # --------------------------------------------------------------------------- @@ -105,6 +123,13 @@ def test_gateway_exposes_port_4000(services): ) +def test_backend_exposes_port_4001(services): + ports = services["backend"].get("ports", []) + assert any("4001" in str(p) for p in ports), ( + f"backend service should expose port 4001, got ports={ports}" + ) + + # --------------------------------------------------------------------------- # Gateway environment variables # --------------------------------------------------------------------------- @@ -122,6 +147,18 @@ def test_gateway_has_store_model_in_db(services): ) +def test_backend_has_database_url(services): + env = services["backend"].get("environment", {}) + assert "DATABASE_URL" in env, "backend service must have DATABASE_URL environment variable" + + +def test_backend_has_store_model_in_db(services): + env = services["backend"].get("environment", {}) + assert "STORE_MODEL_IN_DB" in env, ( + "backend service must have STORE_MODEL_IN_DB environment variable" + ) + + # --------------------------------------------------------------------------- # Health checks # --------------------------------------------------------------------------- @@ -132,6 +169,11 @@ def test_gateway_healthcheck_configured(services): assert hc.get("test"), "gateway service must have a healthcheck configured" +def test_backend_healthcheck_configured(services): + hc = services["backend"].get("healthcheck", {}) + assert hc.get("test"), "backend service must have a healthcheck configured" + + def test_ui_healthcheck_configured(services): hc = services["ui"].get("healthcheck", {}) assert hc.get("test"), "ui service must have a healthcheck configured" @@ -147,6 +189,11 @@ def test_gateway_depends_on_db(services): assert "db" in depends_on, "gateway service must depend on the 'db' service" +def test_backend_depends_on_db(services): + depends_on = services["backend"].get("depends_on", []) + assert "db" in depends_on, "backend service must depend on the 'db' service" + + # --------------------------------------------------------------------------- # Supporting services # ---------------------------------------------------------------------------