feat(items): deploy litellm with its own postgres and redis - #17
Draft
amiralis1365 wants to merge 1 commit into
Draft
feat(items): deploy litellm with its own postgres and redis#17amiralis1365 wants to merge 1 commit into
amiralis1365 wants to merge 1 commit into
Conversation
`litellm` ships one stateless container, and its listing says what that costs: virtual keys, teams, budgets and the spend ledger are Prisma rows, so without `DATABASE_URL` the tables never exist and the only credential a deployment has is the master key. This adds `litellm-stack`, the same proxy with the database that makes it accountable and the cache a second replica needs. Three nodes, two wires. `db.dsn` fills `DATABASE_URL` and `cache.url` fills `REDIS_URL`, both `CONNECTION` inputs that never reach the install form. The two outputs are new: `items/postgres` and `items/redis` publish only `address`, a mesh-internal `host:port`, and LiteLLM reads one DSN and one URL. `address` stays beside them, because `dsn` embeds the password and a consumer that only needs to reach the server should not take a copy of it. The proxy differs from the single-container item in four places, each because of the database: `LITELLM_SALT_KEY` exists at all (nothing is encrypted at rest without one), `STORE_MODEL_IN_DB` keeps model definitions in Postgres rather than a config file this container cannot receive, `DISABLE_SCHEMA_UPDATE` is deliberately unset so the proxy applies its own schema, and the readiness budget grows to 120s because the Prisma setup runs before uvicorn binds the port. Redis is wired and does not switch itself on. `redis_usage_cache` is assigned in one place, reachable only from the `litellm_settings.cache is True` branch of `load_config`, and no environment variable reaches that flag in this release or in the current one. A config file cannot be shipped either — the image's ENTRYPOINT ends in `exec litellm "$@"` and a Musher `command` overrides CMD while inheriting ENTRYPOINT, so everything this component can pass arrives as a CLI argument. `save_config` writes `litellm_settings` to the `config` table when `STORE_MODEL_IN_DB` is set, so enabling it once is durable and applies to every replica; the reasoning is carried in the component so it is not re-derived. Closes #16 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAbh92nGTa3XKBQh2r1jUU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #16.
litellmdeploys one stateless container. Virtual keys, teams, budgets, ratelimits and the per-key spend ledger are Prisma rows, so with no
DATABASE_URLthe tables are never created and the only credential a deployment has is
LITELLM_MASTER_KEY— one shared secret, no per-client attribution, nothing toinvoice from. The existing listing says as much and leaves it there. This adds
litellm-stack: the same proxy with the database that makes it accountable, andRedis beside it for the state a second replica needs.
It is also the first item in the corpus that is a graph. All 13 others are
single-node with
connections: {}, sofromRole/fromOutputresolution andthe two compatibility axes were exercised only by
tests/rules.test.tsagainstsynthetic items.
What this adds
dbpostgres:17.10-alpinegeneral.standard.smallcacheredis:8.8.1-alpinegeneral.standard.smallwebghcr.io/berriai/litellm:v1.83.14-stable.patch.3general.standard.smalldatabaseUrl ← db.dsn,redisUrl ← cache.urlProducers first, because map order is graph order and the two nodes the wires
come from should read before the node they arrive at.
parametersstays empty:the merged
USERinputs are already the form this item wants — databasepassword, user and name, plus two generated LiteLLM keys — and an authored block
would have to restate all five to add nothing. Neither wired input appears
there, which is the point.
The two new outputs
items/postgresanditems/redispublish onlyaddress, a mesh-internalhost:port. LiteLLM reads one DSN and one URL, so this item's copies add:db.dsnSTRING/CONNECTION_STRING/POSTGRES, sensitiveDATABASE_URLis the whole of the database configuration this container needscache.urlSTRING/CONNECTION_STRING/REDISurlis the one redis-py argument carrying host and port togetherBoth are
DERIVED: the host and port are not knowable until the platform placesthe node, so no value can be written. Component §6.2 forbids an output that
depends on a value received over an inbound connection, and neither does —
the credentials
dsncarries are that node's ownUSERinputs. Blueprint§4.2's worked example wires a Postgres node's
connectionString, so a composedDSN is the shape the contract was written for.
addressstays beside both.dsnembedsPOSTGRES_PASSWORDand is markedsensitive; a consumer that only needs to reach the server should keep taking the
address rather than a copy of the password.
cache.urlis not sensitive and thecomponent says why: the server runs with no
requirepasson aPRIVATEendpoint, so there is no password for the URI to carry.
What the database changes about the proxy
Four differences from the single-container item, each caused by the database:
LITELLM_SALT_KEYadded, generatedSTORE_MODEL_IN_DB=TrueDISABLE_SCHEMA_UPDATEdeliberately unsetinitialDelaySeconds30 → 120DATABASE_URLset,proxy_cli.run_serverruns the Prisma schema setup before uvicorn binds 4000, so on a first deploy nothing answers until the migration chain finishes against an empty databaseThe probe stays on
/health/livelinesseven though the database is what wouldmake
/health/readinessmeaningful. That route is declared withdependencies=[Depends(user_api_key_auth)], so onceLITELLM_MASTER_KEYis setit answers 401 to a probe carrying no key and the deployment would never come
up.
/health/livelinesscarries no such dependency.LITELLM_MODE=PRODUCTIONis set because the default,DEV, is the one valuethat makes the CLI call
load_dotenv()on startup, reading a stray.envoutof the working directory.
Redis is wired and does not switch itself on
Stated here rather than found in review.
redis_usage_cache— the client theproxy shares tpm/rpm counters, spend and router state through — is assigned in
exactly one place,
ProxyConfig._init_cache, reachable only from thelitellm_settings.cache is Truebranch ofload_config. No environmentvariable reaches that flag in
v1.83.14-stable.patch.3or inv1.99.1, thecurrent release, which was read to check whether the gap had closed.
A
config.yamlcannot be shipped either. The published image config isEntrypoint: ["docker/prod_entrypoint.sh"],Cmd: ["--port","4000"], and thatscript ends in
exec litellm "$@"; per spec ADR-0010 a Mushercommandoverrides the image's
CMDwhile inheriting itsENTRYPOINT, so every word thiscomponent can pass arrives as a
litellmCLI argument and there is nowhere towrite a file for
--configto name.What closes it is the other wire. With
STORE_MODEL_IN_DBset,ProxyConfig.save_configwriteslitellm_settingsto theconfigtable and_update_config_from_dbmerges it back at every start, so enabling the cacheonce — from the Admin UI or a single
POST /config/update— is durable andapplies to every replica.
cache_paramsis left empty on purpose: an emptyblock is what sends LiteLLM to the environment,
_redis_kwargs_from_environment()maps every
REDIS_<ARG>variable onto a redis-py argument, andRedisCachedrops a null host, port and password before connecting — so
REDIS_URLalone isthe whole connection. The listing says this in plain words and the component
carries the reasoning.
Rejected
A newer LiteLLM tag.
v1.97.1addedgeneral_settings.coordination_redisand an admin API that persists it, which is a nicer switch than
litellm_settings.cache. It is still not zero-touch — it is also read fromconfig or the database — so it buys no automation, and it would take this item
off the stable track for nothing.
v1.83.14-stable.patch.3is the highestvX.Y.Z-stabletag the project publishes, which is why the existing item pins it.A
JOBnode seeding the config row. It would have to insert intoLiteLLM_Configbefore the proxy has migrated the schema that creates it, andthe contract orders no nodes.
Adding
dsnandurltoitems/postgresanditems/redis. The README asksfor one item per pull request, and the item model requires per-item component
copies regardless, so the divergence is inherent rather than introduced here.
Validation
npm test— 400 tests, 0 failures, against the tip ofmusher-dev/specfetchedat run time.
npm run typecheckclean. The per-item suite covers the newsurface: every connection resolves at both ends and the two fit — §4.2 and
the install form covers what a deploying user must supply — §5.2, §5.3.
The
capabilityphase is out of reach here as always: whethergeneral.standard.smallis offered and whether the slug is free are decided atsync.
Noted, not fixed here
items/litellm/components/litellm.yamltells the deploying user that "LiteLLMprefixes it with
sk-". It does not — the master key is compared verbatim withsecrets.compare_digest, and thesk-assertion inuser_api_key_authappliesonly to virtual keys. Harmless in practice, wrong in the description, and its
own one-item change.
🤖 Generated with Claude Code
https://claude.ai/code/session_01AAbh92nGTa3XKBQh2r1jUU