AI API User and Deployment Manual
This manual covers the deployment in which the main SkyLab Compose stack integrates LiteLLM. Regular users obtain ccai_* keys from SkyLab; operators manage Docker from the project root, and model connections are centralised in vllm-service/models.json. Unless stated otherwise, every deployment command below is run from the repository root.
1. Request flow and file locations
flowchart LR
U[User: ccai key] --> N[SkyLab nginx: 8082]
N --> B[Backend: key validation and rate limiting]
B -->|restricted service key| L[LiteLLM: Compose internal network litellm:4000]
L -->|local upstream key| V[Local vLLM: 8103 / 8104]
L -->|per-host upstream key| R[Remote vLLM: IP or domain]
B -->|PgBouncer| C[(SkyLab DB)]
L -->|direct to db:5432| D[(Dedicated LiteLLM DB)]
LiteLLM shares the Compose skylab internal network with the backend and worker; the backend calls it at http://litellm:4000. LiteLLM connects directly to its own dedicated database on the same PostgreSQL instance (db:5432), bypassing PgBouncer. On the host, only 127.0.0.1:4000 is exposed, for health checks, key issuance and admin tooling. LiteLLM is not behind nginx: users always go through SkyLab /api/v1/ai-proxy, where the backend validates the ccai_* key and applies rate limiting before forwarding. LiteLLM's admin UI, /key/* and health APIs are not exposed publicly; when you need the admin UI, connect to 127.0.0.1:4000 from the deployment host itself or through an SSH tunnel.
| File | Purpose | Day-to-day maintenance |
|---|---|---|
docker-compose.yml | SkyLab production Docker entry point; includes the original LiteLLM Compose | docker compose from the repo root |
vllm-service/litellm/docker-compose.yml | LiteLLM service definition; also kept as the standalone entry point | Edit once, shared by both launch modes |
.env | URLs and the restricted service key used by SkyLab backend/worker to reach LiteLLM | Keep existing values; never store LiteLLM admin/upstream keys here |
vllm-service/litellm/.env | LiteLLM master key, salt, DB and every upstream key | Injected only into the LiteLLM container |
vllm-service/models.json | List of local and remote model connections | Add/remove models, adjust aliases or IPs |
vllm-service/litellm/config.template.yaml | Shared timeout, retry and health-check policy | Edit only when the policy changes |
vllm-service/litellm/config.yaml | LiteLLM routing generated from the list and the template | Keep in place; never edit directly |
scripts/prepare-ai-stack.sh | --init-env fills in keys; pre-flight checks and production config generation; --start creates the DB, issues/syncs the service key and starts everything | Run before deploying or changing routes |
config.yaml, models.json and the real .env files are all ignored by Git. Examples and templates may be committed. The root .dockerignore excludes the inference directories and every layer's .env so that model weights and secrets never end up in the SkyLab build. The LiteLLM .env is deliberately not merged into the main .env: backend, worker and prestart read the main .env, and keeping them separate ensures that high-privilege keys only reach the gateway.
2. Filling in addresses and keys
The AI section of the main .env:
AI_API_BASE_URL=http://litellm:4000
AI_API_API_KEY=<restricted service key starting with sk-; generated automatically by --init-env>
LITELLM_RUNTIME_BASE_URL=http://litellm:4000
LITELLM_RUNTIME_API_KEY=<the same restricted service key; filled in automatically by --init-env>
# If this field already exists it must match AI_API_API_KEY; it can be omitted otherwise.
LITELLM_SERVICE_API_KEY=<the same restricted service key>
AI_API_PUBLIC_BASE_URL=https://campus.example.edu
BACKEND_HOST_PORT=8000
REDIS_HOST_PORT=6379AI_API_PUBLIC_BASE_URL is the SkyLab root URL that users can reach (.env.example sets http://localhost:8082). The full API base URL is that address plus /api/v1/ai-proxy. If the backend runs directly on the host, change both gateway URLs to http://127.0.0.1:4000; in main-Compose mode always use the service name litellm (the pre-flight check rejects 127.0.0.1 and the old host.docker.internal).
AI_API_API_KEY is chosen by the deployment and registered in LiteLLM: --start uses the master key to create a Virtual Key in LiteLLM with this value (alias campus-ai-api-service); if it already exists, the model allowlist is synced to the aliases currently in models.json. When reusing an existing LiteLLM database, simply fill in the key that was originally issued to SkyLab.
The LiteLLM .env:
LITELLM_MASTER_KEY=<gateway admin key; generated automatically by --init-env>
VLLM_UPSTREAM_API_KEY=<API_KEY of the vLLM on the inference host; same as .env.API for local models>
DATABASE_URL=postgresql://litellm:<URL-encoded password>@db:5432/litellm
LITELLM_SALT_KEY=<random key created on first deployment and kept fixed afterwards; generated automatically by --init-env>
# Only needed by remote models that use api_key_env; not needed when using apikeys.
REMOTE_LAB_API_KEY=<API_KEY of the remote vLLM>
# Optional: host-side port (bound to 127.0.0.1 only) for health checks and admin tooling.
# LITELLM_HOST_PORT=4000
# Optional: a verified tag or digest for reproducible upgrades/rollbacks.
# LITELLM_IMAGE=litellm/litellm:<tested-tag>--init-env only fills in entries that are missing or still hold example values (replace-with-*, ai-api-secret-*); existing real values are never touched, so it is safe to run repeatedly. Upstream vLLM keys cannot be generated: for local models they are copied from .env.API, and keys for remote hosts must be entered by hand.
| Key | User / service | Scope |
|---|---|---|
ccai_* | Regular user → SkyLab | Personal API access approved by SkyLab |
AI_API_API_KEY | SkyLab → LiteLLM | Models allowed by the LiteLLM Virtual Key |
LITELLM_MASTER_KEY | Operators → LiteLLM | Gateway administration and Virtual Key issuance |
VLLM_UPSTREAM_API_KEY, remote keys | LiteLLM → inference hosts | Each upstream's model API |
Never substitute one class of key for another. Regular users receive a ccai_* key and do not need to know inference host IPs or service keys. When LITELLM_RUNTIME_API_KEY is not set, the admin-side runtime observability features stay disabled.
DATABASE_URL and LITELLM_SALT_KEY
DATABASE_URL is a database connection string, not the stored records themselves. LiteLLM uses it to persist Virtual Key configuration and validation data, usage and spend records, user/team settings and its own schema. Whether usage records include request content depends on LiteLLM's logging settings; do not assume it stores only token counts. The database must be a dedicated DB with its own login role. It must not point to the SkyLab application DB, and SkyLab Alembic must never be run against it.
LITELLM_SALT_KEY is used by LiteLLM to encrypt and decrypt some sensitive settings and upstream credentials it stores; it is not a user API key. Keep it fixed, and back it up and restore it together with the DB; replacing it makes the existing encrypted data undecryptable. It does not automatically encrypt all logs. See the LiteLLM encryption FAQ.
In a same-host deployment, LiteLLM and the main Compose PostgreSQL are on the same internal network, so the host in the URL is db:5432, unrelated to POSTGRES_HOST_PORT. Do not use pgbouncer (LiteLLM's Prisma needs session semantics) or 127.0.0.1 (inside the container that points to itself). When the host is db, --start automatically creates the dedicated role and database using the account, password and database name in the URL (if they already exist, the role password is aligned with the URL), and verifies that the role has no table-creation privilege on the SkyLab database; LiteLLM runs its own schema migration on startup. For a remote DB, enter that host's IP/domain and connection parameters and keep the existing DB and salt; external DBs are created by their own administrators and the script will not touch them. The integrated Compose does not migrate databases automatically.
3. Local and remote model list
The example configuration (vllm-service/models.json.example) defines two local models, gemma-4-31b (8103) and qwen3-14b (8104), plus the two remote entries shown below; the model names used in this manual are examples, and the actual public names are whatever models.json and the /models response say. The following are field examples; merge them into the existing JSON array and keep the existing models' GPU, context, parser and other tuning parameters.
Local entries default to deployment = local:
{
"alias": "gpt-oss-20B",
"deployment": "local",
"served_model_name": "gpt-oss-20B",
"model_name": "./AImodels/gpt-oss-20B",
"api_port": 8103,
"gpu_memory_utilization": 0.4,
"litellm": {"rpm": 10},
"capabilities": {"chat": true}
}Remote vLLM entries need no model_name, api_port or local GPU configuration:
{
"alias": "remote-lab-chat",
"deployment": "remote",
"served_model_name": "lab-chat-model",
"api_base": "http://192.0.2.20:8103/v1",
"api_key_env": "REMOTE_LAB_API_KEY",
"litellm": {"rpm": 10},
"capabilities": {"chat": true}
}To let a remote model carry its own key directly, use a literal apikeys instead:
{
"alias": "remote-lab-private",
"deployment": "remote",
"served_model_name": "lab-private-model",
"api_base": "http://192.0.2.21:8103/v1",
"apikeys": "<API key of that remote model>",
"litellm": {"rpm": 10},
"capabilities": {"chat": true}
}192.0.2.20 is a documentation example address and must be replaced with the real host. The remote host should start the model first, listen on an interface the gateway can reach, and allow connections from the gateway's source address; confirm that its /v1/models really serves lab-chat-model. When neither apikeys nor api_key_env is given, VLLM_UPSTREAM_API_KEY is used; with api_key_env the key is read from the LiteLLM .env, and with apikeys it is written straight into the generated litellm/config.yaml. apikeys and api_key_env cannot be set at the same time. Because apikeys is plaintext, neither models.json nor the generated config.yaml may be committed or copied to an untrusted location. The generators always write config.yaml with mode 0600 (owner read/write only); keep models.json the same way (chmod 600 vllm-service/models.json).
alias is the name callers put in the model field and must be unique across all entries; local served_model_name and api_port must be unique as well. Different remote hosts may use the same upstream model name/port, but their public aliases must differ. api_base must include /v1, and credentials must not be embedded in the URL. The current generator uses the hosted_vllm provider and this example targets remote vLLM; cloud-native providers or other protocols require extending the generator, and entering an IP does not convert the protocol automatically. capabilities is descriptive metadata: tools, reasoning or multimodal features only work if the model and the vLLM parser actually support them.
The local launcher skips remote entries and does not consume local GPU. A fully remote deployment does not need to run the cluster launcher. Routing for both local and remote models is handled by LiteLLM.
4. Production launch and taking over an existing standalone gateway
Requires Linux Docker Engine, Docker Compose 2.20 or later, and a Python with PyYAML and python-dotenv. The script prefers vllm-service/.venv/bin/python; you can also point it at an interpreter with AI_STACK_PYTHON=/path/to/python. Relative mount paths in Compose resolve against the directory of the included file; see the Docker include reference.
Three-step deployment (same for fresh and existing deployments)
# 1. Fill in keys and addresses: only missing or example values are written, real values are left alone (safe to repeat)
bash scripts/prepare-ai-stack.sh --init-env
# 2. Enter the upstream keys that --init-env prompts for (e.g. the DGX VLLM_UPSTREAM_API_KEY) and prepare models.json
# 3. Pre-flight checks, config generation, DB creation, LiteLLM start, service key issuance/sync, then start the main Compose
bash scripts/prepare-ai-stack.sh --startThe main .env must first be created from .env.example with the required SkyLab parameters filled in; if the LiteLLM .env does not exist, --init-env creates it from the template. --init-env generates automatically:
- the LiteLLM
LITELLM_MASTER_KEY(starting withsk-) andLITELLM_SALT_KEY; DATABASE_URL(litellmaccount, random password,db:5432/litellm);AI_API_API_KEYandLITELLM_RUNTIME_API_KEYin the main.env(the samesk-key), and sets both gateway URLs tohttp://litellm:4000.
--start runs, in order:
- Verifies root/gateway key isolation, service key consistency, local upstream key consistency with
.env.API, DB name/account isolation and required remote keys (upstreams are not queried; run--check-only --check-upstreamsseparately when needed); - Generates the production
config.yamland the Prometheus vLLM targets (monitoring/prometheus/targets/vllm.json); - When the
DATABASE_URLhost isdb, starts the main Compose PostgreSQL, waits for it to accept TCP connections, then creates the dedicated role and database (or aligns the password); - Recreates LiteLLM to load the new routes and waits for
/health/readinessto report the database as connected (the first run performs the migration first); - Registers
AI_API_API_KEYin LiteLLM using the master key (aliascampus-ai-api-service); if it already exists, syncs its model allowlist to the aliases currently inmodels.json; - Starts the main project with
docker compose up -d --build.
It does not stop gateways belonging to other projects: if a standalone campus-litellm is running, the script aborts the start and leaves the existing service alone. Stop it first, then rerun:
docker compose -f vllm-service/litellm/docker-compose.yml \
--env-file vllm-service/litellm/.env stop litellm
bash scripts/prepare-ai-stack.sh --start
docker compose ps
curl -fsS http://127.0.0.1:4000/health/readinessVerify only, without changing any file:
bash scripts/prepare-ai-stack.sh --check-only --check-upstreamsIf the local models are not running yet, run bash vllm-service/start_multi_model_cluster.sh first; skip this for fully remote deployments. LiteLLM runs inside a container, so local vLLM must listen on an interface Docker can reach: set API_HOST=0.0.0.0 in .env.API and use the firewall to restrict 8103/8104 to the host and the Docker network. The pre-flight check rejects configurations bound only to loopback.
Reusing an existing LiteLLM database
Keep the original LITELLM_MASTER_KEY, LITELLM_SALT_KEY and DATABASE_URL (for an external host enter its IP/domain; the script will not try to create it), and put the service key originally issued to SkyLab into AI_API_API_KEY and LITELLM_RUNTIME_API_KEY in the main .env. --init-env leaves real values untouched; --start only syncs that key's model allowlist.
Upgrading from the old host-network deployment
The old settings AI_API_BASE_URL=http://host.docker.internal:4000 and [email protected]:5433/... are unreachable under the internal-network architecture. Run --init-env once: it rewrites the gateway URL to http://litellm:4000 and the database host to db:5432 (account and password unchanged), then run --start.
Manual deployment workflow
The workflow itself (.github/workflows/deploy-pve-test.yml) is described in Deployment. For the AI stack: the runner needs /opt/skylab/.env, /opt/skylab/vllm-service/models.json and, with local models, /opt/skylab/vllm-service/.env.API. The workflow runs --init-env against /opt/skylab first, so the generated keys are written back there and persist across deployments (a missing LiteLLM .env is created from the template); it fails at this step if upstream keys are still missing. It then copies the files into the checkout and runs --start. The runner needs python3 -m venv and, the first time keys are filled in, write access to /opt/skylab. Manage the GPU model processes on the deployment host, outside the runner's working directory, which a checkout may wipe.
5. Changing connections, restarting and rolling back
To add a model or change an IP: edit models.json, add the matching key to the LiteLLM .env, then run:
bash scripts/prepare-ai-stack.sh --start--start recreates LiteLLM to load the new routes and syncs the SkyLab service Virtual Key's model allowlist to the new alias list; no separate /key/update call is needed. If you only recreate with docker compose up -d --force-recreate litellm, the allowlist is not synced and new models remain unavailable to SkyLab users. Changing the model itself, the GPU or the local listening port also requires restarting the local inference cluster. After editing the main .env, run docker compose up -d --force-recreate backend worker so the containers pick up the new values. A plain restart does not re-inject .env.
| Operation | Root-directory command / effect |
|---|---|
| Check status | docker compose ps |
| Gateway logs | docker compose logs --tail 100 -f litellm |
| Stop only the gateway | docker compose stop litellm |
| Recreate the gateway | docker compose up -d --force-recreate litellm |
| Stop the main Docker stack | docker compose down, including the taken-over LiteLLM |
Monitoring: "Resource Monitoring → System Health" lists the status of the AI Gateway (LiteLLM) and of every model; when a model's upstream inference service (for example the DGX) is unreachable, a system alert is raised (and emailed to administrators when alert email is enabled in the governance settings). With the monitoring stack enabled, the Grafana "SkyLab AI" dashboard shows SkyLab request volume/errors/latency plus LiteLLM and vLLM engine metrics; the vLLM scrape targets are generated automatically by --start from models.json, and the remote host's firewall must allow the deployment host to reach the inference port (the same rule LiteLLM needs). See System Monitoring for details.
down does not stop host vLLM processes and does not delete an external LiteLLM DB. Do not use down -v as the routine stop command. When the config is missing, the Compose bind mount fails outright instead of accidentally creating a config.yaml/ directory.
Falling back to standalone deployment: first run docker compose stop litellm at the root, then run docker compose up -d from vllm-service/litellm/, keeping the same salt and config. Standalone mode has no db service, so DATABASE_URL must then point at a PostgreSQL host reachable from that container instead of db:5432. LiteLLM is the only AI API gateway; there is no other fallback entry point.
6. Requesting and calling the API as a regular user
Users request a key on the AI API page; the steps, the fields and the validity periods each role can choose are in the AI API page manual. Points that matter when calling the API directly:
- The key list only returns prefixes; the plaintext key is shown only to its owner.
- When requesting through the API, students must send
durationexplicitly (the default, permanent, is not allowed for students). The validity period starts at approval. - Approval re-checks the duration against the applicant's current role, so a pending request whose duration is no longer allowed can only be rejected and submitted again.
- After rotation the old key stops working immediately; update the programs that use it at the same time.
The request-management endpoints under /api/v1/ai-api/* use the SkyLab login bearer token; the inference endpoints below use ccai_*. These are two different kinds of authentication. Set the base_url of your SDK or compatible client to https://campus.example.edu/api/v1/ai-proxy, without an extra /v1 segment.
| Method / endpoint (relative to the base URL) | Purpose |
|---|---|
GET /models | List the model IDs available to the current service key |
POST /chat/completions | Chat with messages; SSE streaming available |
POST /completions | Prompt-based text generation; requires upstream support |
POST /responses | Responses format; requires upstream support |
GET /rate-limit/status | Current quota of this key; does not use the generation quota |
GET /usage/my | Usage statistics of the key's owner (last 30 days by default) |
SkyLab does not forward LiteLLM's admin, key, DB or health APIs. Other endpoints such as embeddings are currently outside the public scope of the SkyLab proxy.
curl: model list and chat
export CAMPUS_AI_BASE_URL='http://localhost:8082/api/v1/ai-proxy'
read -rsp 'SkyLab ccai key: ' CAMPUS_AI_API_KEY; echo
export CAMPUS_AI_API_KEY
curl --fail --silent --show-error \
--config <(printf 'header = "Authorization: Bearer %s"\n' "$CAMPUS_AI_API_KEY") \
"$CAMPUS_AI_BASE_URL/models"
curl --fail --silent --show-error \
--config <(printf 'header = "Authorization: Bearer %s"\n' "$CAMPUS_AI_API_KEY") \
-H 'Content-Type: application/json' \
--data '{"model":"gpt-oss-20B","messages":[{"role":"user","content":"請用繁體中文簡述你的功能。"}],"max_tokens":512}' \
"$CAMPUS_AI_BASE_URL/chat/completions"Replace model with an ID returned by /models to switch models. For streaming, add "stream":true and pass --no-buffer to curl to receive data: events line by line. Reasoning models may spend reasoning tokens first; if content comes back empty with finish_reason=length, increase the token budget and check the model parser first rather than concluding that the connection is broken.
Python can call the API over plain HTTP without any particular SDK:
import json
import os
from urllib.request import Request, urlopen
base = os.environ["CAMPUS_AI_BASE_URL"].rstrip("/")
request = Request(
base + "/chat/completions",
data=json.dumps({
"model": "gpt-oss-20B",
"messages": [{"role": "user", "content": "你好"}],
"max_tokens": 512,
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["CAMPUS_AI_API_KEY"],
"Content-Type": "application/json",
},
)
with urlopen(request, timeout=120) as response:
payload = json.load(response)
print(payload["choices"][0]["message"])Reading the rate limits
| Layer | Default in code / contract | What it means |
|---|---|---|
| SkyLab generation | 20 requests per 60-second sliding window for each approved request | The key's rate_limit takes precedence; the site-wide default (AI_API_RATE_LIMIT_PER_MINUTE) applies only when it is unset. Shared by all models and the three generation endpoints |
| Model list | 30 requests per 60 seconds for each approved request | /models has its own quota and does not use the generation quota |
| Rate-limit status | Does not use the generation quota | /rate-limit/status reports the credential's current quota; disabled=true and error have to be read separately |
| Backend admission | Per process: 20 active globally, 10 active per model, 40 waiting | Concurrency and queueing limits, not RPM; the wait/cooldown budget is 70 seconds and the whole request has a 115-second deadline |
| LiteLLM deployment | The generator gives each deployment rpm 10 by default | The source of truth is litellm.rpm in vllm-service/models.json; the generated config is only used at runtime |
| AI navigation | 30 requests per 60 seconds for each logged-in account | /resolve and /intake share the ai-navigation scope; intake does not call the model but still counts |
| AI PVE assistant | The chat route sets no separate RPM dependency | This does not mean unlimited capacity: it is still bound by login permissions, the System AI transport and upstream limits, and the public API's 20 RPM does not apply to it |
The generation quota is keyed by the approved request's request_id, so rotating the key of the same request does not reset it, and different models do not each get their own 20 requests. A normal call and a streaming call each count once; the rate-limit check runs before body and model validation, so some invalid requests use quota as well. Upstream retries are handled by the relay, and the same logical request does not consume SkyLab RPM again. The sliding window does not reset on the hour; the reset_at in the status response is the query time plus one window, not the exact moment the next slot frees up, so query again when you need the remaining value.
20/60, 30/60 and LiteLLM's 10 RPM are code defaults, not measured capacity. The actual limit of a credential is whatever the status response reports. The status exposes neither the generation window length (AI_API_RATE_LIMIT_WINDOW_SECONDS) nor the LiteLLM RPM; ask the operator if a deployment changed them. With several backend processes the total concurrency adds up, so a single sequential check cannot prove capacity.
Operational verification
bash scripts/prepare-ai-stack.sh --check-only --check-upstreams
curl -fsS http://127.0.0.1:4000/health/liveliness
curl -fsS http://127.0.0.1:4000/health/readiness
# With the SkyLab backend running, use the admin key to verify the gateway model list, upstreams and backend connectivity.
read -rsp 'LiteLLM master key: ' LITELLM_MASTER_KEY; echo
export LITELLM_MASTER_KEY
bash scripts/verify-litellm-staging.sh
unset LITELLM_MASTER_KEY
# Smoke-test the public API with an approved SkyLab test key.
export AI_API_SMOKE_KEY="$CAMPUS_AI_API_KEY"
AI_API_PUBLIC_BASE_URL=http://localhost:8082/api/v1 bash scripts/verify-ai-api-cutover.sh
unset AI_API_SMOKE_KEYverify-litellm-staging.sh needs curl and jq; it checks that the gateway's model list equals the aliases in models.json, that every deployment is healthy and that the backend container reaches the gateway. verify-ai-api-cutover.sh needs curl and python3; it lists the models visible to the key and sends one short non-streaming chat completion to each, so it consumes a little model usage (use a dedicated test key). It does not test streaming, /completions or /responses. Run unset CAMPUS_AI_API_KEY when you are done.
7. Troubleshooting
| Symptom | What to check / do |
|---|---|
4000 already in use / two gateways | Check the Compose project with docker ps; stop the old gateway before taking over, or set LITELLM_HOST_PORT |
| Backend cannot reach the gateway | AI_API_BASE_URL should be http://litellm:4000 (the old host.docker.internal no longer applies; run --init-env to rewrite it) |
| Local upstream returns 401 | API_KEY in .env.API must match the LiteLLM upstream key |
| Local upstream unreachable | API_HOST in .env.API must be 0.0.0.0 (the container connects via host.docker.internal), and restrict the engine port with the firewall |
| Remote model connection fails | Check IP/domain, listening interface, firewall, /v1, served model name and key consistency |
| SkyLab 401 / 403 | Check whether the ccai_* key is approved, expired or revoked; whether the service Virtual Key is valid and allows the model (--start re-registers and syncs it) |
| New model missing from the model list | Redeploy with --start: it regenerates the config, recreates LiteLLM and syncs the service key allowlist |
--start stuck waiting for LiteLLM readiness | docker compose logs litellm; usually wrong DATABASE_URL credentials/host, or the first migration is still running |
| Key issuance returns HTTP 400 | Another key already uses the alias campus-ai-api-service: put that key into AI_API_API_KEY, or revoke it in LiteLLM and rerun |
| 429 | SkyLab rate limit or LiteLLM RPM limit; retry after the Retry-After delay |
| 413 / 415 | Request exceeds AI_API_MAX_REQUEST_BODY_BYTES, or Content-Type is not JSON |
| 502 / 503 | Check whether the gateway, upstream and DB are available, then read the backend/LiteLLM logs |
| DB authentication / migration failure | Check the dedicated role, URL-encoded password, DB host and port; do not switch to the SkyLab DB |
| Cannot decrypt after changing the salt | Restore the original salt and the matching DB backup; old ciphertext cannot be repaired with a new salt |
.env changes have no effect | Recreate the affected containers with up -d --force-recreate |
8000 or 6379 port conflict | Set BACKEND_HOST_PORT / REDIS_HOST_PORT; service ports between containers stay unchanged |
LiteLLM only opens port 127.0.0.1:4000 on the host and is unreachable from external networks; traffic between containers always uses the Compose internal network. The normal entry point for users is SkyLab (nginx → backend); LiteLLM is not exposed through nginx. For the full LiteLLM feature set and key management, see Virtual Keys and the vLLM provider.