Why a gateway
If several applications call AI providers directly, each one holds a real provider key, and you cannot see or limit their spending in one place. A gateway fixes that. LiteLLM is a proxy that exposes one OpenAI-compatible API in front of many providers and local models. Applications get their own virtual keys with budgets and model limits. Only the gateway holds the real provider keys, so a leaked app key can be revoked without touching anything else.
You need Docker (see the Docker guide), about 2 GB of memory, and a domain if you publish it.
sudo docker compose version
free -hDownload the quick-start files
This complete example runs the proxy, PostgreSQL for virtual keys and usage, and a private Ollama backend. The local model lets you verify the gateway before buying hosted API access. Allow at least 4 GB RAM for this small model and the stack; larger models need more.
Generate each secret once. Keep .env and the database backup protected and retain the same salt key when restoring. Pin tested image versions before production updates.
mkdir -p ~/litellm && cd ~/litellm
(umask 077; printf 'LITELLM_MASTER_KEY=sk-%s\nLITELLM_SALT_KEY=sk-%s\nPOSTGRES_PASSWORD=%s\nUI_USERNAME=guideadmin\nUI_PASSWORD=%s\n' "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" "$(openssl rand -hex 24)" "$(openssl rand -hex 24)" > .env)
sed 's/=.*/=(hidden)/' .envcat > compose.yaml <<'EOF'
services:
db:
image: postgres:16
environment:
POSTGRES_USER: llmproxy
POSTGRES_DB: litellm
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- database:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U llmproxy -d litellm"]
interval: 5s
timeout: 3s
retries: 20
restart: unless-stopped
ollama:
image: ollama/ollama:latest
environment:
OLLAMA_KEEP_ALIVE: 2m
OLLAMA_NUM_PARALLEL: 1
volumes:
- models:/root/.ollama
restart: unless-stopped
proxy:
image: ghcr.io/berriai/litellm:main-stable
command: ["--config", "/app/config.yaml", "--port", "4000"]
ports:
- "127.0.0.1:4000:4000"
env_file: .env
environment:
DATABASE_URL: postgresql://llmproxy:${POSTGRES_PASSWORD}@db:5432/litellm
STORE_MODEL_IN_DB: "True"
volumes:
- ./config.yaml:/app/config.yaml:ro,Z
depends_on:
db:
condition: service_healthy
restart: unless-stopped
volumes:
database:
models:
EOFcat > config.yaml <<'EOF'
model_list:
- model_name: my-chat-model
litellm_params:
model: ollama/llama3.2:1b
api_base: http://ollama:11434
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
EOF
chmod 600 config.yamlKeep it private, then start it
Only the proxy port is published, on loopback. Database and Ollama ports stay private. Start the stack, download the configured model, then wait for readiness; the first startup creates database tables.
sudo docker compose config --quiet
sudo docker compose up -d
sudo docker compose exec ollama ollama pull llama3.2:1b
sudo docker compose logs --tail 30 proxy
sudo ss -lntp | grep 4000set -a; . ./.env; set +a
curl --fail-with-body -sS http://127.0.0.1:4000/health/readiness -H "Authorization: Bearer $LITELLM_MASTER_KEY"Reach the admin interface
Keep the SSH tunnel open, visit http://localhost:4000/ui and sign in using UI_USERNAME and UI_PASSWORD from your private .env file. Use the administrator account for key management; applications receive limited virtual keys.
ssh -N -L 4000:127.0.0.1:4000 deploy@YOUR_SERVERAdd a model
The example already mounts config.yaml and defines my-chat-model. To use a hosted provider instead, replace the litellm_params below with that provider’s documented model name, add its API key to .env and recreate proxy. Keep provider credentials out of application code.
A dollar budget limits recorded usage only when the provider/model has accurate pricing data; it is not a hard spending guarantee. Local Ollama models have no provider token charge. Add request/token rate limits and provider-side account limits as appropriate.
model_list:
- model_name: my-chat-model
litellm_params:
model: PROVIDER/MODEL_NAME
api_key: os.environ/PROVIDER_API_KEYsudo docker compose config --quiet
sudo docker compose up -d --force-recreate proxy
sudo docker compose logs --tail 30 proxyIssue a limited virtual key
Give each application its own key. Create one through the API with the master key, and set limits: the models it may use, a spending cap and a lifetime. The field names are listed in the project's virtual keys documentation, so check them against your version. The response contains the key, which is shown once, so store it with the application that will use it.
set -a; . ./.env; set +a
curl -s http://127.0.0.1:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{"models":["my-chat-model"],"max_budget":5,"duration":"30d","key_alias":"my-app"}'Send a test request
Call the gateway the same way you would call any OpenAI-compatible API, but with your own base URL and the virtual key. Any client library that lets you set a base URL works.
curl --fail-with-body -sS http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer sk-YOUR-VIRTUAL-KEY" \
-H "Content-Type: application/json" \
-d '{"model":"my-chat-model","messages":[{"role":"user","content":"Say hello in five words."}]}'Publish it with HTTPS
Applications on other servers need an HTTPS address. Follow the reverse proxy guide to proxy a name such as ai.example.com to 127.0.0.1:4000, with long read timeouts and proxy buffering off so streamed answers work. Never give applications the master key, and keep the admin interface path off the public site, for example by only allowing /v1 and /chat paths through the proxy.
location /v1/ {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 600s;
}Operate it
Keep .env, config.yaml and a protected PostgreSQL dump off this VPS. A database-only backup cannot recover the original salt and provider credentials. Verify a restore with the same image version and test that a saved virtual key still works.
Revoke a leaked key through the admin interface or /key/delete using the master key; then confirm requests with that key fail. Read release notes and back up before updating.
cd ~/litellm
set -o pipefail
umask 077
sudo docker compose stop proxy
sudo docker compose exec -T db pg_dump --clean --if-exists -U llmproxy litellm | gzip > litellm-db-$(date +%F).sql.gz
sudo docker compose start proxy
gzip -t litellm-db-$(date +%F).sql.gzcd ~/litellm
sudo docker compose stop proxy
gunzip -c litellm-db-YYYY-MM-DD.sql.gz | sudo docker compose exec -T db psql -v ON_ERROR_STOP=1 -U llmproxy -d litellm
sudo docker compose start proxy
# Recheck /health/readiness and a request using a saved virtual key.sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail 30 proxyDisallowed model rejected: 403
Restored virtual key still authorizes actual model inference
Revoked key rejected: 401Official documentation
These upstream references provide more detail on the commands and workflows in this guide.