# llm-proxy: Postgres, the backend and the frontend, from the published images. # # No .env file is needed: every setting is written here. Edit the values in this # file, then run `docker compose up -d`. CHANGE ME marks what to change before the # first start; the file also starts as it is, but its placeholders are public. A # setting that has a default is written with that default; a setting that is off or # computed by default is a commented-out example. The minimal version of this stack # is docker-compose.minimal.yml. # # Only the proxied API (host port 8080) and the web interface (host port 8081) are # published. Three networks keep each listener to the containers that need it: `web` # joins the frontend and the backend's web API, `db` the backend and Postgres, # `metrics` the backend's /metrics and a scraper, which an operator attaches to it. # The web API and /metrics listen only on their network's address (the backend's # alias there), so a container on another network cannot reach them. The web API # trusts X-Real-IP only because the frontend alone can reach it. services: postgres: image: postgres:17-alpine networks: [db] environment: # The user and database names are also in the backend's connection URL and in # the healthcheck below: keep them to letters, digits and _ - . POSTGRES_USER: llmproxy # CHANGE ME (e.g. openssl rand -hex 24). It must match PGPASSWORD in the # backend. Postgres takes it only on its first start, with an empty volume. POSTGRES_PASSWORD: change-me-db-password POSTGRES_DB: llmproxy volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD", "pg_isready", "-U", "llmproxy", "-d", "llmproxy"] interval: 5s timeout: 3s retries: 10 backend: # The backend and the frontend are released together: keep one version for both. image: yoonaowo/llm-proxy-backend:0.8.4 environment: # The password travels as PGPASSWORD, which the driver reads when the URL # carries none, so it needs no URL encoding. LLMPROXY_DATABASE_URL: postgres://llmproxy@postgres:5432/llmproxy?sslmode=disable # CHANGE ME: the same value as POSTGRES_PASSWORD above. PGPASSWORD: change-me-db-password # CHANGE ME (openssl rand -hex 32), best before adding vendor accounts: at least # 32 bytes. It encrypts the vendor accounts' OAuth credentials in the database. # The placeholder works but is public, and the backend warns while it is set. # Keep the key apart from database backups: changing or losing it means signing # every vendor account in again. LLMPROXY_CREDENTIALS_KEY: change-me-credentials-key-at-least-32-bytes # The proxied LLM API: the one backend listener published. LLMPROXY_LISTEN_ADDR: ":8080" # The web API (/api/*) on the `web` network only, /metrics on the `metrics` # network only; no ports entry publishes either. A scraper must join # `metrics` and scrape http://backend-metrics:9090/metrics. LLMPROXY_WEB_ADDR: "backend-web:8081" LLMPROXY_METRICS_ADDR: "backend-metrics:9090" # Paths upstream requires. Nothing is written under LLMPROXY_RUNTIME_DIR, and # LLMPROXY_AUTH_DIR holds only a vendor sign-in's hand-off file, for about a # second: the vendor accounts are in Postgres. LLMPROXY_RUNTIME_DIR: /var/lib/llmproxy/runtime LLMPROXY_AUTH_DIR: /var/lib/llmproxy/auths # The first administrator's login. While no administrator exists, the backend # creates it and prints a one-time password in its log. Empty disables this. LLMPROXY_BOOTSTRAP_ADMIN_EMAIL: admin@example.com # Where clients reach the proxied API; shown on the Connect page. Behind a # reverse proxy: https://llm-proxy.example.com. LLMPROXY_PUBLIC_API_URL: http://localhost:8080 # false only because this stack serves the web interface over plain http, where # a Secure cookie would not be sent back. Behind TLS, set it to "true". LLMPROXY_COOKIE_SECURE: "false" # "false" turns the email-and-password form off (an OIDC-only installation). LLMPROXY_LOCAL_LOGIN: "true" # Parallel password hashes (about 19 MiB each); unset: the CPU count. # LLMPROXY_PASSWORD_HASH_CONCURRENCY: "4" # The process log's format: "text" (key=value lines) or "json" (one object per line). LLMPROXY_LOG_FORMAT: text # "on" fetches upstream's model catalogue from the internet at start and every # three hours; "off" keeps the one compiled into the build. LLMPROXY_MODEL_CATALOG_UPDATES: "on" # The price catalog (oh-my-pi's model catalog; "off": manual prices only) and # how often it is checked (at least 5m). LLMPROXY_PRICES_CATALOG_URL: https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/catalog/src/models.json LLMPROXY_PRICES_CATALOG_INTERVAL: 6h # OIDC sign-in, off while LLMPROXY_OIDC_ISSUER is unset. To turn it on, uncomment # the lines below: the session key, client id, secret and redirect URL are then # required. # CHANGE ME with OIDC: seals the sign-in cookie; at least 32 bytes # (openssl rand -base64 48). # LLMPROXY_SESSION_KEY: change-me-session-key-at-least-32-bytes # The identity provider's issuer URL. # LLMPROXY_OIDC_ISSUER: https://idp.example.com/realms/example # LLMPROXY_OIDC_CLIENT_ID: llm-proxy # CHANGE ME with OIDC: the client secret from the identity provider. # LLMPROXY_OIDC_CLIENT_SECRET: change-me-oidc-client-secret # https:///api/auth/oidc/callback # LLMPROXY_OIDC_REDIRECT_URL: http://localhost:8081/api/auth/oidc/callback # A group every federated user must be in; unset admits anyone. # LLMPROXY_OIDC_REQUIRED_GROUP: /llm-users # "true" creates an account for an unknown user at the first sign-in. # LLMPROXY_OIDC_ALLOW_SIGNUP: "true" # Rules a signed-up user starts with, comma-separated. # LLMPROXY_OIDC_DEFAULT_POLICY: chatgpt:* # group=rule,rule;group=rule - when set, groups own federated users' rules. # LLMPROXY_OIDC_GROUP_POLICY: /llm-users=chatgpt:*;/llm-claude=claude:*,chatgpt:* # The ID token claim that holds the groups. LLMPROXY_OIDC_GROUPS_CLAIM: groups # The sign-in button's label; unset leaves it to the frontend. # LLMPROXY_OIDC_DISPLAY_NAME: Example SSO networks: web: aliases: [backend-web] db: {} metrics: aliases: [backend-metrics] ports: # Host port of the proxied API. Behind a reverse proxy on the same host: # - "127.0.0.1:8080:8080" - "8080:8080" depends_on: postgres: condition: service_healthy # On SIGTERM, requests in flight get 30 s to finish (streams included), then # writing the queued usage records up to 5 s; give it that long before SIGKILL. stop_grace_period: 45s healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"] interval: 10s timeout: 3s retries: 5 frontend: # The same version as the backend image. image: yoonaowo/llm-proxy-frontend:0.8.4 environment: # The backend's web listener, on the `web` network. BACKEND_ORIGIN: http://backend:8081 # Addresses/CIDRs of your reverse proxy, comma-separated, trusted for # X-Forwarded-For; unset trusts none. # REAL_IP_FROM: 172.16.0.0/12 networks: [web] ports: # Host port of the web interface. Behind a reverse proxy on the same host: # - "127.0.0.1:8081:8080" - "8081:8080" depends_on: backend: condition: service_healthy healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"] interval: 10s timeout: 3s retries: 5 networks: web: db: metrics: volumes: pgdata: