Table of Contents
Root³ Solutions LibreChat Deployment Guide
Stack: LibreChat + LM Studio (Qwen3 27B) + ComfyUI MCP + Admin Panel Host: 192.168.1.203 — Nobara Linux, Podman (no Docker) Deployed: 2026-10-19 / finalized 2026-10-20 URLs: App http://192.168.1.203:3080 — Admin Panel http://192.168.1.203:3090 Account: info@root3.de (ADMIN — first registered user)
1. Folder Structure
One folder, everything inside it. All secrets live ONLY in .env — compose.yaml references them as ${VARS}, never hardcoded [1].
~/librechat/ ├── compose.yaml # Podman Compose — 3 services (api + mongodb + admin-panel) ├── librechat.yaml # LibreChat config — endpoints, MCP, interface ├── boot-librechat.sh # Login autostart script (LLM-first ordering) ├── .env # ALL secrets + env vars (mounted into both api and panel) ├── root_solutions_logo.svg # Your logo — overwrites the built-in logo.svg ├── index.html # Patched LibreChat index (links custom.css) └── custom.css # Logo size + centering override
2. compose.yaml (Complete)
Three services: LibreChat API, MongoDB, and the separate Admin Panel [1]. No secrets in this file — only one ${VAR} reference for the panel session secret, which podman-compose expands from .env at create time (verified working).
services: api: image: ghcr.io/danny-avila/librechat:latest container_name: librechat-app ports: - "3080:3080" depends_on: - mongodb extra_hosts: - "host.containers.internal:host-gateway" volumes: # Config + branding (read-only bind mounts) - ./librechat.yaml:/app/librechat.yaml:ro - ./root_solutions_logo.svg:/app/client/dist/assets/logo.svg:ro - ./index.html:/app/client/dist/index.html:ro - ./custom.css:/app/client/dist/custom.css:ro # Durable data (named volumes — survive container recreation) - librechat-images:/app/client/public/images - librechat-uploads:/app/api/data/uploads - librechat-logs:/app/api/logs # All secrets/env — single source of truth is ./.env - ./.env:/app/.env:ro environment: - HOST=0.0.0.0 - PORT=3080 - MONGO_URI=mongodb://mongodb:27017/LibreChat - ALLOW_REGISTRATION=true restart: "no" mongodb: image: docker.io/library/mongo:latest container_name: librechat-db volumes: - librechat-db-data:/data/db restart: "no" admin-panel: image: registry.librechat.ai/librechat-ai/librechat-admin-panel:latest container_name: librechat-admin ports: - "3090:3000" depends_on: - api extra_hosts: - "host.containers.internal:host-gateway" volumes: - ./.env:/app/.env:ro environment: - PORT=3000 - SESSION_SECRET=${ADMIN_PANEL_SESSION_SECRET} - API_SERVER_URL=http://librechat-app:3080 - VITE_API_BASE_URL=http://192.168.1.203:3080 restart: "no" volumes: librechat-db-data: name: librechat-db-data librechat-images: name: librechat-images librechat-uploads: name: librechat-uploads librechat-logs: name: librechat-logs
Key design decisions:
- restart: “no” everywhere — startup is owned by the boot script; no surprise auto-restarts (matches your open-webui pattern)
- host.containers.internal:host-gateway — containers reach LM Studio (:1234) and ComfyUI (:8188) on the host by name, no hardcoded LAN IP
- Named volumes for all durable data — deleting/recreating containers never touches chats, uploads, or the database [2]
- Fully-qualified image names (docker.io/library/mongo:latest) — prevents the interactive “select an image” prompt from hanging unattended scripts
- Admin panel is a separate URL (:3090), not a tab inside the app — that is LibreChat's official architecture [1]; user creation/management only exists there (or via registration, or the official create-user/delete-user CLI scripts)
3. .env File (Complete — ALL Secrets Here)
# ~/librechat/.env HOST=0.0.0.0 PORT=3080 MONGO_URI=mongodb://mongodb:27017/LibreChat ALLOW_REGISTRATION=true JWT_SECRET=super_secret_token_string_generation_12345 REFRESH_TOKEN_SECRET=super_secret_refresh_token_string_54321 # ⚠️ THE one this image build actually reads for sign-in (see troubleshooting 8a): JWT_REFRESH_SECRET=super_secret_refresh_token_string_54321 ADMIN_PANEL_SESSION_SECRET=08513016b4e0f9fc9d3466f4d10e740efdf490c6d2fae7bc86536b2bb7fc5ea3 ADMIN_PANEL_PORT=3090 ADMIN_PANEL_URL=http://192.168.1.203:3090
Notes:
- Mounted into BOTH the api and admin-panel containers (./.env:/app/.env:ro) — the api's auth code reads secrets from this file [3]
- ALLOW_REGISTRATION=true is what makes the Sign-up link appear; flip to false once your account exists to lock the door (users can still be created via the panel/CLI)
- ADMIN_PANEL_PORT / ADMIN_PANEL_URL are currently unused by any service — kept as documentation of intent
4. librechat.yaml (Complete)
version: 1.1.2 interface: privacyPolicy: externalUrl: 'https://yourcompany.com' openNewTab: true termsOfService: externalUrl: 'https://yourcompany.com' openNewTab: true endpoints: custom: - name: "LM Studio" # host.containers.internal = the host machine, from inside the container baseURL: "http://host.containers.internal:1234/v1" apiKey: "lm-studio" models: fetch: true # live model list from LM Studio default: ["qwen/qwen3.8-27b"] # your actual model ID titleConvo: true summarize: false mcpServers: comfyui: type: stdio command: npx args: - "-y" - "comfyui-mcp" # real npm package (verified Oct 2026) - "--comfyui-url" - "http://host.containers.internal:8188"
Notes:
- baseURL is the correct field name (not “url”) [4]
- fetch: true auto-populates the model picker from whatever LM Studio has loaded
- “mcp-server-comfyui” does NOT exist on npm — comfyui-mcp is the real package, supports remote ComfyUI via –comfyui-url [5]
- First MCP call needs internet inside the container (npx download); later calls are cached
5. Logo Sizing (custom.css)
LibreChat renders logo.svg at a fixed small size with no config key to change it. Fix: patched index.html loads an extra stylesheet, which overrides the img rule [6].
5a. How it was set up
# 1) Copy the real index.html out of the container (while running) podman exec librechat-app cat /app/client/dist/index.html > ~/librechat/index.html # 2) Inject a link tag into <head> sed -i 's|</head>|<link rel="stylesheet" href="/custom.css"></head>|' ~/librechat/index.html # 3) The override itself (file: ~/librechat/custom.css) cat > ~/librechat/custom.css <<'EOF' img[src*="logo.svg"] { height: 140px !important; width: auto !important; display: block !important; margin-left: auto !important; margin-right: auto !important; } EOF
5b. Tuning
Edit the px value in ~/librechat/custom.css → hard refresh browser (Ctrl+Shift+R). Bind mount reads live — no container recreate needed.
⚠️ After every LibreChat image update, re-extract index.html from the fresh container and re-apply the sed line, then down/up once (logo patch would otherwise revert on new images).
6. boot-librechat.sh (Final Version)
Runs via Nobara Login Scripts (System Settings → Autostart → Login Scripts). Ordering matches your existing start-ai-new.sh pattern: LLM first, then all three containers, then health check. Idempotent — safe to fire multiple times at login.
#!/bin/bash # boot-librechat.sh — start LibreChat stack after login (LM Studio first) LOG=/var/log/librechat-boot.log exec >> "$LOG" 2>&1 echo "=== $(date) librechat-boot ===" PATH="$HOME/.local/bin:$PATH" MODEL="qwen/qwen3.8-27b" # --- 1. LM Studio server up? ------------------------------------------ if ! curl -s --connect-timeout 2 http://localhost:1234/v1/models > /dev/null; then echo "⏳ LM Studio not responding — starting server..." lms server start || echo "⚠️ lms server start failed." for i in $(seq 1 15); do curl -s --connect-timeout 2 http://localhost:1234/v1/models > /dev/null && break sleep 2 done fi # --- 2. Model loaded? (no-op if already in RAM) ------------------------ echo "⏳ Loading model into LM Studio..." lms load "$MODEL" || echo "⚠️ lms load failed." sleep 5 # --- 3. Start all LibreChat containers --------------------------------- if podman ps -a --filter "name=librechat-app" --format '{{.Names}}' | grep -q librechat-app; then echo "📦 Starting LibreChat containers..." podman start librechat-app librechat-db librechat-admin 2>&1 else echo "⚠️ Containers not found — creating via compose first..." cd ~/librechat && podman-compose --in-pod true up -d \ || { echo "❌ compose failed."; exit 1; } fi # --- 4. Health check ----------------------------------------------------- for i in $(seq 1 10); do if curl -s --connect-timeout 2 http://localhost:3080/ > /dev/null; then echo "✅ LibreChat API is up." break fi [ "$i" -eq 10 ] && { echo "❌ API down — last 30 log lines:"; podman logs --tail 30 librechat-app; exit 1; } sleep 5 done echo "=== $(date) done ==="
Autostart setup (one-time, already done):
- Nobara System Settings → Autostart → + Add New → Login Script → ~/librechat/boot-librechat.sh → say Yes to make executable
- To disable: trash the entry, or rm ~/librechat/boot-librechat.sh
Log file: /var/log/librechat-boot.log — tail -f /var/log/librechat-boot.log
7. Command Quick Reference
| Action | Command |
|---|---|
| Start stack (manual) | podman start librechat-app librechat-db librechat-admin |
| Stop stack | podman stop librechat-app librechat-db librechat-admin |
| Restart containers after changing yaml/env | cd ~/librechat && podman-compose –in-pod true down && podman-compose –in-pod true up -d |
| Update LibreChat image (keeps all data) | cd ~/librechat && podman-compose –in-pod true down && podman pull ghcr.io/danny-avila/librechat:latest && podman-compose –in-pod true up -d |
| View app logs | podman logs –tail 100 librechat-app |
| View mongo logs | podman logs –tail 50 librechat-db |
| View panel logs | podman logs –tail 50 librechat-admin |
| View boot log | tail -f /var/log/librechat-boot.log |
| Pod status | podman pod ps |
| Remove stack, KEEP data | cd ~/librechat && podman-compose –in-pod true down |
| Nuke everything including data | above + podman volume rm librechat-db-data librechat-images librechat-uploads librechat-logs |
| List all users + roles | podman exec librechat-db mongosh –quiet –eval 'db.getSiblingDB(“LibreChat”).users.find({}, {email:1, roles:1}).toArray()' |
| Check LM Studio live models | curl -s http://localhost:1234/v1/models |
Notes:
- “Restart after yaml/env change” = applies your config edits (this is the row you added — note correct hyphen in -in-pod)
- “Update image” additionally pulls the new release; afterwards re-extract index.html for the logo patch (section 5b)
- All down/rm operations never touch named volumes — data is safe
8. Troubleshooting Log
8a. Sign-in 500 "secretOrPrivateKey must have a value"
Cause: this image build signs refresh tokens with process.env.JWT_REFRESH_SECRET, not REFRESH_TOKEN_SECRET (confirmed at node_modules/@librechat/data-schemas/dist/index.mjs:5989). Registration worked because no token is issued at signup. Fix: JWT_REFRESH_SECRET=<value> in .env + recreate. ✅ Applied.
8b. "Please select an image" prompt hangs scripts
Cause: bare image names trigger podman's interactive registry picker; without a TTY it blocks forever. Fix: fully-qualified names in compose.yaml (docker.io/library/mongo:latest, ghcr.io/danny-avila/librechat:latest). ✅ Applied.
8c. code-interpreter image 403 Forbidden
Cause: ghcr.io/danny-avila/code-interpreter:latest no longer exists in the registry; the official compose no longer ships it either [1]. Fix: service dropped — not needed for chat + ComfyUI MCP. ✅ Applied.
8d. Logo too small on login page
Cause: built-in CSS sizes logo.svg fixed; no interface config key exists for it. Fix: patched index.html → custom.css → img[src*=“logo.svg”] override, 140px + centered. ✅ Verified in browser.
8e. podman-compose --pod not recognized
Cause: the dnf package (v1.6.0) is containers/podman-compose v1.x — different flag than the pipx build. Fix: use –in-pod true before the subcommand: podman-compose –in-pod true up -d. ✅ Applied.
8f. "agent_id is required in request body"
Cause: message sent before the LM Studio endpoint was selected in the model picker — request routed to the agents endpoint. Fix: new conversation, select model explicitly first. ✅ Resolved.
8g. Admin panel image 403 on ghcr
Cause: ghcr.io/danny-avila/librechat-admin-panel:latest does not exist there; correct registry is registry.librechat.ai/librechat-ai/librechat-admin-panel:latest per the official compose [1]. Fix: corrected image reference. ✅ Container runs, panel serves at :3090.
8h. Logs show "OpenID check failed: 500 .../openid/check"
Cause: known upstream bug — LibreChat issue #14504, fixed in PR #14507. Maintainer confirms it is log noise, NOT the login blocker (panel treats any non-ok response as “SSO unavailable” and falls back to email/password). Action: none — ignore until an image update includes the fix.
8i. Admin panel login: form submits, nothing happens
⚠️ OPEN ITEM. API logs show [Login] [Login successful] [info@root3.de] repeated — credentials accepted, auth works; the panel never establishes a browser session. Prime suspect: session cookies flagged secure over plain HTTP (candidate fixes: SESSION_COOKIE_SECURE=false / ADMIN_PANEL_SESSION_COOKIE_SECURE=false in .env) or panel-side session wiring. Secondary check pending: users.find({}, {email:1, roles:1}) to confirm exactly one user holds the ADMIN role (per #14504 thread, only the first registered user gets it). Status: unresolved — next debugging step is the Network tab on :3090/login (failed request + status code) plus librechat-admin logs during an attempt.
9. Status Overview
| Component | Status | Note |
|---|---|---|
| Logo (big + centered) | ✅ verified in browser | custom.css, 140px |
| Auth: register + sign-in | ✅ working | JWT_REFRESH_SECRET fix |
| LM Studio chat | ✅ “Hello!” response confirmed | Qwen3 27B via fetch:true |
| ComfyUI MCP server | ✅ “running on stdio” in logs | comfyui-mcp package |
| Admin account | ✅ info@root3.de (first user = ADMIN) | panel is separate URL :3090 |
| Admin panel service | ✅ running, serves :3090 | login still open (8i) |
| Autostart (LLM first) | ✅ Nobara Login Script | /var/log/librechat-boot.log |
| Data persistence | ✅ named volumes | survives recreation/updates |
| Podman Desktop grouping | ✅ pod_librechat, 3 containers | –in-pod true |
| — | — | — |
| ComfyUI image generation end-to-end | ⏳ not yet tested | last open functional test |
