Self-hosted private AI assistant: LibreChat + Firecrawl + SearXNG + Camofox, fed by a custom Lemonade LLM endpoint.
- Spec:
docs/superpowers/specs/2026-06-27-libre-stack-design.md - Plan:
docs/superpowers/plans/2026-06-27-libre-stack.md
Twelve containers, all in one compose.yaml, all on a single bridge network
libre-stack. Four host ports are exposed (3080, 3090, 3002, 8080); everything
else is internal-only and reached via Zoraxy subdomain routing.
| # | Service | Role | Image | Host port |
|---|---|---|---|---|
| 1 | librechat |
Chat UI / API | librechat/librechat:latest |
3080 |
| 2 | admin-panel |
User / role admin UI | registry.librechat.ai/clickhouse/librechat-admin-panel:latest |
3090 |
| 3 | mongo |
LibreChat DB | mongo:8.0.20 |
— |
| 4 | meilisearch |
RAG index | getmeili/meilisearch:v1.35.1 |
— |
| 5 | firecrawl-api |
REST scrape/crawl (harness) | ghcr.io/firecrawl/firecrawl:latest |
3002 |
| 6 | firecrawl-playwright |
Headless browser | ghcr.io/firecrawl/playwright-service:latest |
— |
| 7 | firecrawl-redis |
BullMQ queue | redis:7-alpine |
— |
| 8 | firecrawl-postgres |
Firecrawl metadata | ghcr.io/firecrawl/nuq-postgres:latest |
— |
| 9 | firecrawl-rabbitmq |
Firecrawl queue broker | rabbitmq:3-management |
— |
| 10 | searxng |
Metasearch (JSON-only) | searxng/searxng:latest |
8080 |
| 11 | mcp-firecrawl |
MCP server for Firecrawl | ghcr.io/firecrawl/firecrawl-mcp-server:latest |
— |
| 12 | camofox-browser |
Stealth browser + MCP server | ghcr.io/redf0x1/camofox-browser:latest |
— |
Data flow:
User → Zoraxy (TLS) → librechat:3080
│
├─→ mongo:27017 (users, messages, memory)
├─→ meilisearch:7700 (RAG index)
├─→ tesseract:5000 (OCR)
├─→ searxng:8080 (web_search plugin → JSON)
├─→ mcp-firecrawl:3000 (scrape/crawl tools)
├─→ mcp-camofox:8000 (browser tools)
│ └─→ camofox-browser:8080
└─→ 192.168.31.246:13305 (Lemonade LLM, via host LAN)
User → Zoraxy → firecrawl-api:3002 (only via mcp-firecrawl from librechat;
also direct from Zoraxy as escape hatch for debugging)
LibreChat is the only service that talks to Lemonade (the LLM). Firecrawl and Camofox are tools called by LibreChat's agents, not by users directly.
MCP tool surface: LibreChat's default agent inherits all globally registered
MCP servers plus the built-in web_search tool.
web_search→ hits SearXNG JSON endpoint.firecrawl_scrape,firecrawl_crawl,firecrawl_map,firecrawl_extract→ mcp-firecrawl.camofox_navigate,camofox_click,camofox_fill,camofox_screenshot,camofox_extract_text→ mcp-camofox.
# 1. Clone and enter the repo
cd /home/john/Projects/libre-stack
# 2. Bootstrap (copies .env.example → .env, generates secrets, verifies
# Lemonade is reachable, pulls and brings up the stack, waits for health)
./bin/bootstrap.sh
# 3. Confirm the stack is healthy
./bin/smoke.sh
# 4. Wire Zoraxy (see "Zoraxy config" below) and visit
# https://chat.example.combin/bootstrap.sh is idempotent: re-running it after a partial bring-up is
safe. It prints the admin URL and generated credentials on success.
Four subdomains, each pointing to a host port exposed by compose.yaml.
Configure these in your Zoraxy admin UI (/admin):
| Subdomain | Backend | Notes |
|---|---|---|
chat.example.com |
http://<host>:3080 |
Primary public entry — LibreChat UI + API. |
admin.example.com |
http://<host>:3090 |
LibreChat admin panel (user/role management). |
search.example.com |
http://<host>:8080 |
SearXNG escape hatch for direct debugging. |
scrape.example.com |
http://<host>:3002 |
Firecrawl API escape hatch for direct debugging. |
Production traffic should flow only through chat.example.com and admin.example.com. The SearXNG
and Firecrawl host ports exist mainly so an operator can poke at them directly
when something is wedged behind the agent loop. They are not part of the
public UX.
Set ADMIN_PANEL_SESSION_COOKIE_SECURE=true in .env once admin.example.com
is served over HTTPS so the panel's session cookie locks down. Leave it false
for plain-HTTP local testing.
Zoraxy terminates TLS, handles ACME, and forwards per-subdomain. Make sure the
host running Libre Stack is reachable on ports 3080, 3090, 3002, and 8080 from
where Zoraxy runs (typically the same box or the same LAN).
Lemonade is not a container in this stack — it runs on your LAN as a
standalone OpenAI-compatible service (a custom OpenAI-compatible API on
http://192.168.31.246:13305/v1 by default).
- LibreChat's
librechat.yamlis wired to that URL via theOPENAI_API_BASE/ custom endpoint config. - The host running Libre Stack must be able to reach
192.168.31.246:13305.bin/bootstrap.shverifies this on every run. - Lemonade's API is OpenAI-compatible, so any future swap (local Ollama, a
vLLM server, a remote provider) is a single
baseURLchange inlibrechat.yaml.
To swap to local Ollama later: add an ollama container to compose.yaml and
change the endpoints.custom[0].baseURL to http://ollama:11434/v1.
libre-stack/
├── README.md # this file
├── compose.yaml # all 13 services + volumes + networks
├── librechat.yaml # LibreChat app config (model list, MCP servers)
├── .env.example # template for .env (no secrets committed)
├── .gitignore
├── bin/
│ ├── bootstrap.sh # first-run / idempotent bring-up
│ ├── backup.sh # snapshot stateful volumes + databases
│ ├── update.sh # pull images and recreate stack
│ └── smoke.sh # post-up health + integration checks
└── docs/superpowers/
├── specs/2026-06-27-libre-stack-design.md
└── plans/2026-06-27-libre-stack.md
| Volume | Owner | Purpose |
|---|---|---|
mongo-data |
mongo |
LibreChat DB |
meili-data |
meilisearch |
RAG index |
firecrawl-postgres-data |
firecrawl-postgres |
Firecrawl metadata |
firecrawl-redis-data |
firecrawl-redis |
BullMQ queues (transient) |
firecrawl-playwright-data |
firecrawl-playwright |
browser cache |
camofox-data |
camofox-browser |
browser profile, cookies |
librechat-uploads |
librechat |
user-uploaded files (RAG source) |
No volume for SearXNG — its only stateful bits are the in-process limiter and the redis-backed secret key, neither needs persistence in our config.
./bin/update.shdocker compose pulldocker compose up -d- Healthcheck grace period: 2 minutes for LibreChat and Firecrawl. On
failure the script prints an error and leaves the stack as-is. Manual
rollback to an earlier tag is left to the operator (LibreChat is pinned;
Firecrawl and SearXNG use
:latest).
BACKUP_DIR=/var/backups/libre-stack ./bin/backup.shOutput to ${BACKUP_DIR}/YYYY-MM-DD/, retain 14 days. Intended for host cron,
not run inside the stack.
| Volume | Contents | Method |
|---|---|---|
mongo-data |
users, messages, memory | mongodump |
meili-data |
RAG index | Meili snapshot API |
firecrawl-postgres-data |
Firecrawl metadata | pg_dump |
camofox-data |
browser profile | tar |
librechat-uploads |
user files | tar |
firecrawl-redis-data |
BullMQ queues | skip — reconstructible |
firecrawl-playwright-data |
browser cache | skip |
Example cron entry (daily at 03:17):
17 3 * * * BACKUP_DIR=/var/backups/libre-stack /home/john/Projects/libre-stack/bin/backup.sh >> /var/log/libre-stack-backup.log 2>&1
./bin/smoke.sh # health + integration checks
RUN_E2E=1 ./bin/smoke.sh # also runs a full LibreChat → Lemonade prompt round-tripChecks (exits non-zero on first failure):
curl /api/healthon librechat, firecrawl-api, searxng.- SearXNG
GET /search?q=test&format=jsonreturns aresultsarray. - Firecrawl
POST /v1/scrapeagainsthttps://example.comreturns markdown. - mcp-camofox
GET /toolsreturns a tool list. - End-to-end (gated by
RUN_E2E=1): LibreChat → Lemonade prompt round-trip with a known answer.
Not in scope for v1. docker stats + Zoraxy logs are sufficient. Prometheus
and Grafana are added when a real need surfaces.
bin/bootstrap.sh fails on the Lemonade check.
The host can't reach 192.168.31.246:13305. Verify network reachability
(curl http://192.168.31.246:13305/v1/models) and that the Lemonade service
is up. LibreChat cannot start without this — it's a hard dependency.
LibreChat healthcheck never goes green after update.sh.
Check docker compose logs librechat. Most common cause: Meili or Mongo
isn't ready yet (LibreChat waits on those internally). Give it the full
2-minute grace, then docker compose ps to see which container is
unhealthy. Roll back by pinning LibreChat to the previous tag in
compose.yaml and re-running ./bin/update.sh.
SearXNG returns empty results.
Check docker compose logs searxng. SearXNG's JSON endpoint needs at least
one enabled engine in searxng-settings.yml. If you've swapped the
settings volume, restart the container.
Firecrawl POST /v1/scrape times out.
firecrawl-api waits on firecrawl-worker, which waits on
firecrawl-playwright. Verify the whole chain is healthy
(docker compose ps). If only the worker is wedged, restart it in place:
docker compose restart firecrawl-worker.
bin/smoke.sh reports MCP /tools is empty on mcp-camofox.
The Camofox MCP image may not yet have a published release — progress.md
notes this is deferred to the end-to-end run. Verify the image exists:
docker pull ghcr.io/redf0x1/camofox-mcp:latest.
Backup snapshot directory is missing or partial.
Check BACKUP_DIR is writable by the cron user and that the named volumes
exist (docker volume ls | grep libre-stack).
.envis never committed..env.exampleis the template;bin/bootstrap.shgenerates secrets withopenssl rand -hex Non first run. Rotate them by editing.envand restarting the affected container.- All Lemonade traffic stays on the LAN (
192.168.31.246); nothing crosses the public internet for inference. - SearXNG and Firecrawl host ports (
8080,3002) are published on the host's external interface by default (0.0.0.0). If you don't need direct LAN access to the escape hatches, tighten the bind incompose.yamlto127.0.0.1:8080:8080and127.0.0.1:3002:3002— Zoraxy on the same host can still reach them via loopback. Do not expose them on a public IP — they are escape hatches with no auth on the direct path. - LibreChat admin credentials are printed by
bin/bootstrap.shon first run and stored nowhere else. Change the admin password immediately after the first login via the LibreChat UI. - Backup archives under
${BACKUP_DIR}may contain chat history, uploaded files, and browser cookies (Camofox profile). Treat that directory as sensitive: encrypt at rest, restrict filesystem permissions, and never sync it to a public bucket. - Zoraxy is responsible for TLS, rate limiting, and bot protection on the public subdomains. This stack assumes Zoraxy is hardened separately.
Status: v1 design approved 2026-06-27. See docs/superpowers/specs/ for
the full design and docs/superpowers/plans/ for the implementation plan.