ENTH
PART 2 · Setup

Config walkthrough + virtual keys and budgets

Config walkthrough · virtual keys and budgets

TL;DR — a 30-second summarytldr

This chapter is the "answer key for every line" — the secrets file .env and the gateway file litellm.yaml: together these two files decide how secure the system is, how fast it runs and how it breaks. We will walk them line by line with explanations

Rule number 1: real values live in .env only (gitignored, chmod 600) — the example file holds nothing but CHANGE_ME values that must all be replaced

A virtual key is a secondary key issued per channel — each one has 3 properties: it allows only the models you specify, it has its own daily budget ceiling, and it can be revoked or rotated without touching the master key — this is why the 5-channel system lives together in an orderly way: one gate, many doors, every door metered

When to usewhen to use

Do this after bootstrap (chapter 3) — bootstrap will not raise the stack until .env is clean (no CHANGE_ME left)

  • Open the .env file with an editor on the VPS and replace block by block following the sections below
  • Generate keys in this pattern: random from openssl rand -hex 16, prefixed with sk-

Issue keys as soon as the stack is up (chapters 3-4), and issue new ones every time you add a channel — chapters 7-8 will use these keys

KeyWho uses itAllowed modelsCeiling/day
BOM_KEY_LINELINE botthai-chat-rt$2
BOM_KEY_TELEGRAMTelegram botthai-chat-rt$2
BOM_KEY_MT5MT5 EA (read-only)single analysis model$1
BOM_KEY_WRITERn8n article pipelinebom-writer$5
BOM_KEY_WEBUIWeb chatcurated list$10

How-tohow to

The .env file — the full version with explanations (from configs/.env.example):

# ---------- LiteLLM ----------
# The master key = the homeowner's key; generate it once and keep it safe
LITELLM_MASTER_KEY=sk-CHANGE_ME   # openssl rand -hex 16, then prefix sk-
LITELLM_UI_USERNAME=admin
LITELLM_UI_PASSWORD=CHANGE_ME     # a strong password for the /ui page

# ---------- Postgres ----------
POSTGRES_USER=litellm
POSTGRES_PASSWORD=CHANGE_ME       # the database lives inside the container network, not exposed
DATABASE_URL=postgresql://litellm:...@postgres:5432/litellm

# ---------- Open WebUI ----------
WEBUI_SECRET_KEY=CHANGE_ME        # used to sign web-chat user sessions

# ---------- Mac via Tailscale ----------
# Format: http://100.x.y.z:11434  (the Mac's Tailscale IP from chapter 2)
# Iron rule: never a public IP
MAC_OLLAMA_BASE_URL=http://100.CHANGE_ME.CHANGE_ME.CHANGE_ME:11434

# ---------- Tier 3 (cloud fallback) ----------
# Want no cloud at all = leave it empty (100% hard-local)
CLOUD_FALLBACK_API_KEY=CHANGE_ME_or_leave_empty

# ---------- Per-channel keys ----------
# Generate them later via /key/generate, then paste them here (chapter 4)
BOM_KEY_LINE=sk-CHANGE_ME
BOM_KEY_TELEGRAM=sk-CHANGE_ME
BOM_KEY_MT5=sk-CHANGE_ME
BOM_KEY_WRITER=sk-CHANGE_ME
BOM_KEY_SMOKE=sk-CHANGE_ME

Remember: in this house, logs and reports only ever say "key loaded, length N" — never print the real value

After editing, run the 30-second check: chmod 600 .env and docker compose up -d, then confirm LiteLLM parses the config; if anything gets stuck → chapter 9

Issue keys straight through the LiteLLM API (one command per key):

curl -X POST http://127.0.0.1:4000/key/generate \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key_alias": "line-bot",
       "max_budget": 2.0,
       "budget_duration": "1d",
       "models": ["thai-chat-rt"]}'

# The response carries a "key" field = the real key -> paste it into .env and the n8n credential store
# (in reports/logs say only "key loaded, length N")

Principle: keys live only in .env (VPS) or the n8n credential store — never embedded in workflow JSON or code

Check the cumulative spend every day (this is the "receipt" source of numbers for the whole book):

-- SQL on the LiteLLM Postgres (run via docker exec)
SELECT date_trunc('day', "startTime") AS day,
       count(*) AS calls,
       round(sum(spend)::numeric, 4) AS usd
FROM "LiteLLM_SpendLogs"
GROUP BY 1 ORDER BY 1 DESC LIMIT 30;

Real results from the first week of September 2026: 1,370 calls/$1.10, 1,470/$0.66, 3,448/$2.76 (the peak), 494/$0.31

If anything gets stuck → chapter 9

Config exampleconfig example

The litellm.yaml file — the most important parts, abridged (the full 134-line version lives in the repo):

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY   # referenced from env only
  max_budget: 5.0        # total cloud spend ceiling of $5/day — the anti-exploding-bill guard
  budget_duration: 1d

litellm_settings:
  drop_params: True      # [Real lesson] parameters the proxy does not know get "silently dropped"
                         # a probe once proved repeat_penalty=8.0 went through while presence_penalty vanished
  fallbacks: [{ "thai-chat": ["thai-chat-cpu", "thai-chat-cloud"] }]  # the 3-step ladder from chapter 1

model_list:
  # Tier 1 — the Mac (free, primary)
  - model_name: thai-chat
    litellm_params:
      model: ollama_chat/qwen38-chat
      api_base: os.environ/MAC_OLLAMA_BASE_URL   # Tailscale only
      num_ctx: 32768        # context floor
      num_predict: 2048     # chat answer ceiling

  # Bot route: think off + a tight answer ceiling
  # [Real lesson] think on + low num_predict = the model burns its answer quota on "inner thoughts" and replies empty
  - model_name: thai-chat-rt
    litellm_params:
      model: ollama_chat/qwen38-chat
      extra_body: { "think": false }
      num_predict: 1024
      timeout: 51

  # Writer route (article pipeline): a high ceiling so long reports are not cut off
  - model_name: bom-writer
    litellm_params:
      num_ctx: 32768
      num_predict: 8192    # safe completion floor (chapter 6)

The shape of the whole file = one route per use case, one behavior per route; the trade-offs happen here, not in the Mac's Modelfile

Using a key on the caller side is easy — every channel calls the same place, differing only in the key and the model name:

# every channel points at: https://llm.your-domain/v1  (OpenAI-compatible 100%)
curl https://llm.example.com/v1/chat/completions \
  -H "Authorization: Bearer $BOM_KEY_LINE" \
  -d '{"model": "thai-chat-rt",
       "messages": [{"role":"user","content":"How is gold doing this noon?"}]}'

Use the wrong key or exceed the budget and the proxy answers 401/403 immediately — there is no way to drift into someone else's money

Pro tip — field-tested trickspro tip

★ PROD LESSON
The value that broke on a typo — repeat_penalty 8.0 (a typo for 1.05) wrecked entire answers, and it "passed" because the proxy forwards everything — the lesson: never trust that a config which does not error is a correct config; always confirm with a bench run (chapter 6)
★ PRO TIP
Want 100% hard-local? — delete only the thai-chat-cloud block in model_list and leave CLOUD_FALLBACK_API_KEY empty — the system then keeps core chat data on our own machine, apart from web search and the cloud fallback, which go out under external providers' terms — the trade-off is the bot going quiet while the Mac is down
★ PROD LESSON
The ceiling that was never touched — the $5/day total ceiling (max_budget in litellm.yaml) has been in place since day one; the highest real peak was $2.76 = 55% of the ceiling — setting a ceiling up front does not mean you will use it all; it means that on the day something misbehaves, the bill is still locked
★ PRO TIP
Painless key rotation — because every channel holds its own key, rotating the web-chat key never touches the bots — if you suspect a leak, suspend that one key first, then issue a fresh one (the small-blast-radius idea)

FAQfaq

Q: I deployed with wrong values already — do I have to start over? — No. Fix .env and run docker compose up -d again; the only one that is painful to change is the master key, because every place that references it must be updated

Q: Where should .env be kept? — On the VPS only; never copy it to a laptop or Drive. For a real backup use a vault (e.g. Vaultwarden on the NAS)

For other questions see the FAQ

Q: If a key really leaks, how much is the damage? — Capped at that key's ceiling ($1–10/day) plus the time until we spot it in the spend log — the models the key allows are restricted too, so it cannot be used to call anything pricier

Q: Where is the dashboard? — At LiteLLM's /ui (behind Cloudflare Access); it has both the key list and the daily spend graph

See alsosee also

Figuresfigures

Bot request path through litellm.yaml
The thai-chat-rt route is the middle path of this diagram
The LiteLLM API page, version 1.100.0, as actually used in production
The version shown is the version actually running on the VPS — every endpoint needs a key before it can be called
Diagram of the fabric of 5 virtual keys
One proxy, many doors — every door has its own meter

Real screen recording of this lesson · click play to hear the summary · not investment advice

▶ Watch the manual video: https://youtu.be/O4hM39cgDCY