====== Root³ Solutions LibreChat Deployment Guide ======
**Stack**: LibreChat + LM Studio (Qwen3 27B) + ComfyUI MCP
**Host**: 192.168.1.203 — Nobara Linux, Podman (no Docker)
**Deployed**: 2026-10-19
**URL**: http://192.168.1.203:3080
===== 1. Folder Structure =====
All files live in one folder under the rootai home directory. No systemd units, no .env file required separately — everything is inline in compose.yaml or in the .env file mounted into the container.
~/librechat/
├── compose.yaml # Podman Compose — 2 services (api + mongodb)
├── librechat.yaml # LibreChat config — endpoints, MCP, interface
├── boot-librechat.sh # Login autostart script (LLM-first ordering)
├── .env # Secrets + env vars (mounted to /app/.env)
├── root_solutions_logo.svg # Your logo — overwrites built-in logo.svg
├── index.html # Patched LibreChat index (links custom.css)
└── custom.css # Logo size + centering override
===== 2. compose.yaml (Full) =====
Two services only: the LibreChat API and MongoDB. Code-interpreter was dropped (ghcr image returns 403 — it has been removed from the registry [1]).
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
# Environment file with all secrets
- ./.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"
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"// on all services — you control startup via the boot script, no auto-restart on crash or reboot (matches your open-webui pattern)
* //host.containers.internal:host-gateway// — lets the container reach LM Studio and ComfyUI on the host machine by name instead of hardcoding a LAN IP [2]
* Named volumes for all durable data — safe to delete/recreate containers without losing chats, uploads, or the database [3]
* //docker.io/library/mongo:latest// fully qualified — prevents the interactive "select an image" prompt that hangs unattended scripts
===== 3. .env File =====
All secrets and auth settings go here. Mounted to ///app/.env// inside the container. This is how LibreChat reads its config in this build (the //@librechat/data-schemas// package expects it there, not only in process env) [4].
# ~/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
# THIS IS THE ONE THAT ACTUALLY WORKS FOR SIGN-IN ON THIS IMAGE BUILD:
JWT_REFRESH_SECRET=super_secret_refresh_token_string_54321
**⚠️ Critical note on JWT_REFRESH_SECRET:**
This image build's //@librechat/data-schemas// package (line 5989 of //dist/index.mjs//) calls //jwt.sign()// with //process.env.JWT_REFRESH_SECRET//, **not** //REFRESH_TOKEN_SECRET//. Without this line, registration works but sign-in returns a 500 "secretOrPrivateKey must have a value" error. Both lines are kept for clarity; only //JWT_REFRESH_SECRET// is actually read by the signing code.
===== 4. librechat.yaml (Full) =====
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 # pulls 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 package on npm (verified Oct 2026)
- "--comfyui-url"
- "http://host.containers.internal:8188"
**Notes:**
* //baseURL// is the correct field name (not "url") [5]
* //fetch: true// means the model picker auto-populates from whatever is loaded in LM Studio — no need to hardcode every model ID [6]
* The original "mcp-server-comfyui" package does not exist on npm; //comfyui-mcp// is the real one, supports remote ComfyUI via --comfyui-url [7]
* First MCP call needs internet inside the container for npx to download the package — subsequent calls are cached
===== 5. Logo Sizing (custom.css) =====
LibreChat renders logo.svg at a fixed small size in its built-in CSS and there is no config key to change it. The fix: patch index.html to load an extra stylesheet, then override the img rule [8].
==== 5a. How it was set up ====
# 1) Copy the real index.html out of the container (while it's running)
podman exec librechat-app cat /app/client/dist/index.html > ~/librechat/index.html
# 2) Inject a link tag into
sed -i 's|||' ~/librechat/index.html
# 3) Create the override
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 the size ====
Edit //~/librechat/custom.css// on disk, change the px value, hard-refresh the browser (Ctrl+Shift+R). No container recreate needed — it's a bind mount, reads live.
===== 6. boot-librechat.sh (Autostart Script) =====
Runs from Nobara Login Scripts (System Settings → Autostart → Login Scripts). Order matches your existing //start-ai-new.sh// pattern: LLM first, then 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 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
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/api/version > /dev/null \
|| 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):**
* Nobara System Settings → Autostart → **+ Add New** → Login Script
* Select //~/librechat/boot-librechat.sh//
* Say **Yes** when asked to make it executable
* To disable: trash the entry, or //rm ~/librechat/boot-librechat.sh//
**Log file:** ///var/log/librechat-boot.log// — check with //tail -f /var/log/librechat-boot.log//
===== 7. Command Quick Reference =====
^ Action ^ Command ^
^ Start stack (manual) ^ cd ~/librechat && podman start librechat-app librechat-db ^
^ Stop stack ^ podman stop librechat-app librechat-db ^
^ View logs (app) ^ podman logs --tail 100 librechat-app ^
^ View logs (mongo) ^ podman logs --tail 50 librechat-db ^
^ View boot log ^ tail -f /var/log/librechat-boot.log ^
^ Check pod status ^ podman pod ps ^
^ Update LibreChat image (keeps all data) ^ cd ~/librechat && podman-compose down && podman pull ghcr.io/danny-avila/librechat:latest && podman-compose --in-pod true up -d ^
^ Remove stack, keep data ^ cd ~/librechat && podman-compose down ^
^ Nuke everything including data ^ above + podman volume rm librechat-db-data librechat-images librechat-uploads librechat-logs ^
^ Query users in DB ^ podman exec librechat-db mongosh --quiet --eval 'db.getSiblingDB("LibreChat").users.findOne({}, {email:1, roles:1})' ^
^ Check LM Studio models live ^ curl -s http://localhost:1234/v1/models ^
===== 8. Troubleshooting Log =====
==== 8a. Sign-in returns 500 "secretOrPrivateKey must have a value" ====
**Cause:** This LibreChat image build reads //process.env.JWT_REFRESH_SECRET//, not //REFRESH_TOKEN_SECRET//. The variable name is specific to the //@librechat/data-schemas// package version bundled in this image (confirmed at //dist/index.mjs:5989//).
**Fix:** Add //JWT_REFRESH_SECRET=// to //~/librechat/.env//, then recreate.
==== 8b. "Please select an image" prompt hangs the boot script ====
**Cause:** Bare image name (e.g. mongo:latest) triggers podman's interactive registry picker when no local match is found. With no TTY at login time this blocks forever.
**Fix:** Always use fully qualified names in compose.yaml — //docker.io/library/mongo:latest//, //ghcr.io/danny-avila/librechat:latest//.
==== 8c. Code-interpreter image returns 403 Forbidden ====
**Cause:** //ghcr.io/danny-avila/code-interpreter:latest// has been removed from the registry (403 = repo no longer public/exists). The current official LibreChat compose file does not include a code-interpreter service [9].
**Fix:** Drop the service entirely — it's not needed for chat + ComfyUI MCP.
==== 8d. Logo too small on login page ====
**Cause:** LibreChat's built-in CSS sizes logo.svg to a fixed compact dimension. No config key exists in interface object structure [10].
**Fix:** Mount a patched index.html that links custom.css, then override with //img[src*="logo.svg"] { height: 140px !important; }//.
==== 8e. podman-compose --pod flag not recognised ====
**Cause:** Podman Desktop does not bundle podman-compose; the dnf package is v1.x (containers/podman-compose) [11], which uses a different flag name than the pipx version.
**Fix:** Use //podman-compose --in-pod true up -d// instead of //--pod//.
==== 8f. "agent_id is required in request body" on first chat ====
**Cause:** The conversation was created before the LM Studio endpoint was selected from the model picker, so the request routed to the agents endpoint.
**Fix:** Start a new conversation and explicitly select LM Studio + model from the picker before sending.
===== 9. What Works / What's Verified =====
^ Component ^ Status ^ Notes ^
^ Logo (big + centered) ^ ✅ Verified in browser ^ custom.css mount, 140px ^
^ Auth (register + sign-in) ^ ✅ Working ^ JWT_REFRESH_SECRET fix applied ^
^ LM Studio chat ^ ✅ "Hello!" response confirmed ^ Qwen3 27B loaded ^
^ ComfyUI MCP server ^ ✅ "running on stdio" in logs ^ comfyui-mcp package ^
^ Admin access ^ ✅ First registered user is admin [1] ^ Role dropdown = viewer scope, not account role ^
^ Autostart (LLM first) ^ ✅ Nobara Login Script added ^ /var/log/librechat-boot.log ^
^ Data persistence ^ ✅ Named volumes ^ Survives container recreation ^
^ Podman Desktop grouping ^ ✅ pod_librechat, 2 containers ^ --in-pod true ^
**Not yet tested:** ComfyUI image generation end-to-end from inside LibreChat (ask chat to generate an image).
======