LibreChat sandbox — Inference Co-op customization

Source of truth for the LibreChat evaluation instance at librechat-test.inference.coop (Cloudron app 4023ccbf-d57a-4500-97df-14de9cf3516e, appstore package ai.librechat.cloudronapp@1.4.1, which bundles LibreChat v0.8.8 on image cloudron/ai.librechat.cloudronapp:202610150224320000).

This repo exists so the sandbox never drifts from what is deployed: the config file here is byte-identical to /app/data/librechat.yaml, the MCP server here is byte-identical to /app/data/mcp/server.py, and the Docker image is built from this directory.

Architecture: what lives where

Layer Location Survives image update? Repo mirror
App config /app/data/librechat.yaml (volume) yes librechat.yaml
Env vars (env-file wins!) /app/data/env (volume, sourced LAST by start.sh) yes changed lines documented below
Env vars (Cloudron-managed) cloudron env (process env) yes documented below
MCP servers /app/data/mcp/server.py + /app/data/mcp/image_server.py (volume) yes mcp/
Brand assets (logo, favicons) baked into image overlay no — rebuilt per image assets/
CloudronManifest.json repo only (used by cloudron update) n/a byte-identical copy of the OFFICIAL appstore manifest

Precedence gotcha: start.sh does source /run/librechat/env AFTER the process env, and /app/data/env is appended into it — so a value defined in /app/data/env overrides the same cloudron env variable. That is why ALLOW_EMAIL_LOGIN=false must be set in /app/data/env, not (only) via cloudron env.

Deploy a config change (no image rebuild needed)

B64=$(base64 -w0 librechat.yaml)
cloudron --server my.inference.coop --token "$CLOUDRON_TOKEN" exec \
  --app 4023ccbf-d57a-4500-97df-14de9cf3516e \
  -- bash -c "echo '$B64' | base64 -d > /app/data/librechat.yaml"
cloudron --server my.inference.coop --token "$CLOUDRON_TOKEN" restart \
  --app 4023ccbf-d57a-4500-97df-14de9cf3516e

Always diff the repo file against the live one after deploying. Always deploy via base64 — heredocs and sed inserts have corrupted this file twice (literal $(...) / bad indentation → LibreChat silently falls back to defaults with only one log line).

Verify after every change:

cloudron logs --app <id> --lines 60 | grep -iE 'custom config|error|invalid|MCP'
curl -s https://librechat-test.inference.coop/api/config   # appTitle, emailLoginEnabled, socialLogins

Build + deploy the image (brand assets only)

cd /tmp/librechat-custom          # THIS dir: cloudron update reads CloudronManifest.json here
git add -A && git commit -m "..." && git push
SHA=$(git rev-parse --short HEAD)
cloudron builder build --repository registry.inference.coop/co-op/librechat-custom \
  --tag 20261019-$SHA
cloudron --server my.inference.coop --token "$CLOUDRON_TOKEN" update \
  --app 4023ccbf-d57a-4500-97df-14de9cf3516e \
  --image registry.inference.coop/co-op/librechat-custom:20261019-$SHA

Rollback: rerun the update with the previous tag (or the appstore image cloudron/ai.librechat.cloudronapp:<original-tag>).

NEVER edit CloudronManifest.json. cloudron update reads it from this directory and applies it to the app. A hand-edited manifest that omits the addons (postgresql, mongodb, oidc, sendmail, localstorage) strips them from the running app and crash-loops it (mkdir: Read-only file system). That is what broke the sandbox on 2026-10-18 — the manifest, not the overlay. When the appstore package updates, refresh this file with a new copy pulled from the Cloudron API (GET /api/v1/apps/<id> → .manifest) and bump the FROM tag in the Dockerfile to the new manifest.dockerImage.

Env vars (live state)

Set in /app/data/env (volume — these WIN over cloudron env):

  • APP_TITLE="Inference Co-op" (was "LibreChat")
  • ALLOW_EMAIL_LOGIN=false (was true) — password login removed; Cloudron SSO (OIDC) is the only login path
  • SCHEDULES_SINGLE_PROCESS=true — sandbox runs a single replica; silences the scheduler's Redis guard error

Set via cloudron env set (process env):

  • TTS_API_KEY / STT_API_KEY — dedicated LiteLLM virtual key (alias librechat-speech, $5 / 30d budget, restricted to tinfoil/voxtral-tts + tinfoil/whisper-large-v3-turbo). Regenerate with the gateway master key: POST /key/generate {"key_alias":"librechat-speech","models":[...],"max_budget":5,"budget_duration":"30d"} — never put the master key here.
  • IMAGE_GEN_OAI_BASEURL=https://gateway.inference.coop/v1 and IMAGE_GEN_OAI_MODEL=regolo/qwen-image — the agent "OpenAI Image Tools" points at the co-op gateway. No IMAGE_GEN_OAI_API_KEY: members enter their own co-op key in the tool UI (per-member billing, same pattern as the chat endpoint's apiKey: user_provided).
  • SEARXNG_INSTANCE_URL=https://search.inference.coop — referenced from the YAML as ${SEARXNG_INSTANCE_URL} (self-hosted search; keyless JSON API).

Feature notes (LibreChat v0.8.8)

  • Chat models: custom endpoint "Inference Co-op" → gateway, per-member keys, 8 chat modelSpecs (emoji convention shared with models.md). regolo/qwen-image is deliberately NOT in the chat picker (image models are not chat models; chat-calling it was the source of the old 500s).
  • Image generation — two paths:
    1. MCP tool (easiest): inference-image server (tool generate_image) — members select it in the composer's + palette and just ask. No agent setup. First use: save your co-op key in the MCP panel (customUserVars IC_API_KEY, injected per-user into the server process env; startup: false because the key only exists per-user after they configure it). Bills to the member's own key (~1–2¢/image at 1024²).
    2. Agent tool: LibreChat's built-in "OpenAI Image Tools" (env IMAGE_GEN_OAI_BASEURL/IMAGE_GEN_OAI_MODEL → gateway; no admin key, members paste their own). Note LibreChat v0.8.8 renders the generated image only inside the agent conversation.
  • MCP: inference-docs (tool search_docs, starts at boot) and inference-image (tool generate_image, per-user startup). Members may add their own via the MCP Settings panel ("bring your own tools"). Permissions seeded via interface.mcpServers (use/create true, share false).
  • Speech: TTS tinfoil/voxtral-tts (voice casual_female), STT tinfoil/whisper-large-v3-turbo, both via the gateway with the capped key.
  • Web search: SearXNG (self-hosted) + keyless Keenable scraper, no reranker.
  • Colors: interface.theme in the YAML is forward-compatible only — the theme engine ships in LibreChat releases AFTER 0.8.8 (merged to dev, not tagged). The block is silently ignored today and activates by itself when the appstore package bundles a newer LibreChat. No other runtime theming exists in 0.8.8.

Known state / limitations

  • The [schedules] Redis guard error is resolved by SCHEDULES_SINGLE_PROCESS=true (above).
  • The RAG API not reachable warning is expected (no rag_api addon); it only affects LibreChat's own file-RAG, which we do not use.
  • The inference-image MCP server logs Tools: undefined at boot — expected: with startup: false it registers but only spawns per-user after a member saves their key and reinitializes in the MCP panel.
S
Description
Custom LibreChat sandbox with image‑generation button
Readme
1.2 MiB
0 Stars 2 Watchers 0 Forks
Languages
Python 38%
JavaScript 22.3%
Shell 18.7%
Dockerfile 11.1%
CSS 9.9%