This repository contains a highly optimized, production-grade, and resilient multi-container Docker Compose layout designed for home-lab services. It includes search, media streaming, downloads, change detection, and headless browsing engines bound together by a robust, secure environment overriding system.
- SearXNG: A privacy-respecting, self-hosted metasearch engine.
- Valkey: A high-performance, open-source key-value database (fully API-compatible Redis fork) acting as SearXNG's caching and rate-limiting (bot-protection) backend.
- qBittorrent: A lightweight, secure torrent downloader (includes automated, custom Python search engine setups).
- Jellyfin: The ultimate voluntary media system for organizing and streaming your private libraries (configured with host-networking and AMD hardware acceleration).
- ChangeDetection.io: (Optional) Powerful self-hosted website change monitoring engine.
- Flaresolverr: (Optional) Proxy server to bypass DDoS protection mechanisms for scraping and indices.
- Browser: (Optional) Shared Chrome/CDP sidecar — a real browser on tap for scraping and automation, with a web desktop UI. Migrated here 2026-09-07 from its own repo.
- MetaMCP: (Optional) MCP gateway with a bundled hardened Postgres 18. Migrated here 2026-09-07 from its own repo.
- Kali Desktop: (Optional) Full Kali Linux desktop streamed to the browser via Selkies, with AMD/Vulkan GPU acceleration for a Radeon 780M iGPU and the host's
/home/jondmounted in.
Production note: production self-hosting runs on Cloudron (automatic maintenance, updates and backups). This repo is the local Docker + Docker Compose side. The former Coolify fleet is retired; the hard-won operational lessons from it are preserved in
docs/HARD-WON-GOTCHAS.md.
This compose stack is built with the highest standards of production container orchestration:
Every image is pinned to a specific version tag (never :latest), so upgrades are
explicit and reviewable rather than silent. Renovate
runs weekly (.github/workflows/check-upstream-release.yml) to open version-bump PRs
as upstream releases land.
Every container drops all Linux capabilities by default (cap_drop: [ALL]) and adds
back only what its entrypoint actually needs — the s6-overlay/PUID-PGID root→user
privilege-drop set for LinuxServer-style images, or nothing at all for images that
already run as non-root. security_opt: [no-new-privileges:true] is set throughout.
See docs/HARD-WON-GOTCHAS.md for the reasoning and the
traps this avoids (a bare cap_drop: ALL breaks the root→user drop dance).
All service definitions support a modern, 4-layered environment file hierarchy. Variables are evaluated sequentially (later files override/supersede earlier ones), allowing host-specific overrides to be kept strictly separate from the base configurations:
env_file:
- path: ../.env # Global default settings
required: false
- path: ../.env.local # Global machine-specific overrides
required: false
- path: .env # Service-specific default settings
required: false
- path: .env.local # Service-specific machine-specific overrides
required: false- Zero Hardcoded Secrets: Cryptographic keys like
SEARXNG_SECRETare passed dynamically from.envusing environment variables. No secrets are stored insettings.yml. - Privacy-Friendly Directory Mounts: Your physical host storage directories (e.g.
/mnt/...) are kept in your local.envand are strictly excluded from version control via.gitignore. - Clean
.env.example: A fully commented template is provided for a seamless open-source setup experience.
Every container is capped with memory and CPU boundaries using Docker's deploy.resources.limits configuration to prevent memory leaks or background loop bugs from freezing your host system.
To protect your host disk from filling up, all containers are constrained to standard JSON file logging rotations (max-size: "10m", max-file: "3").
Containers running headless Chromium instances (browser-sockpuppet-chrome and flaresolverr) are configured with init: true. This invokes the lightweight Docker init-system to automatically reap zombie child processes.
- Clone this repository to your host machine.
- Copy the template file to
.env:cp .env.example .env
- Open the newly created
.envfile and customize the volume paths (MEDIA_BOOKS,DOWNLOADS_DIR, etc.) to point to your storage directories. - Generate a unique, cryptographically secure key for SearxNG and add it to your
.envunderSEARXNG_SECRET:openssl rand -hex 32
- If you're enabling the optional MetaMCP service, generate its two required secrets the same way and set them under
BETTER_AUTH_SECRETandMETAMCP_POSTGRES_PASSWORD— both fail the container on startup if left as the placeholder.
Bring up the entire stack in the background:
docker compose up -dCheck the health status of all running containers:
docker compose ps| Service | Host Port | Internal Port | Environment Override Key |
|---|---|---|---|
| SearXNG | 8888 |
8888 |
SEARXNG_PORT |
| qBittorrent | 8080 |
8080 |
QBITTORRENT_PORT |
| ChangeDetection | 5000 |
5000 |
CHANGEDETECTION_PORT |
| Flaresolverr | 8191 |
8191 |
FLARESOLVERR_PORT |
| Jellyfin | Host Network | 8096 |
Managed via Host Net |
| Browser (web UI) | 3000 |
3000 |
BROWSER_UI_PORT |
| Browser (CDP) | 9223 |
9223 |
BROWSER_CDP_PORT |
| MetaMCP | 12008 |
12008 |
METAMCP_PORT |
| Daily Stars Explorer | 8080 |
8080 |
DAILY_STARS_PORT |
| Kali Desktop (HTTP) | 3010 |
3000 |
KALI_DESKTOP_HTTP_PORT |
| Kali Desktop (HTTPS) | 3011 |
3001 |
KALI_DESKTOP_HTTPS_PORT |
Optional service, off by default. Uncomment ./kali-desktop/docker-compose.yml
in the root include: block to enable it.
Start it:
docker compose up -d kali-desktopAccess it: open https://localhost:3011 (accept the self-signed cert —
HTTPS is required for clipboard/audio/file transfer). The plain HTTP port
(3010) also works for a quick check but skips those features. Both are
loopback-bound by default; widen only behind a VPN or reverse proxy, since
this container has sudo and full access to Kali's toolkit.
GPU (AMD Radeon 780M): accelerated by default via /dev/dri + Mesa/Vulkan
(no CUDA — that's NVIDIA-only). If KALI_GPU_RENDER_GID/KALI_GPU_VIDEO_GID
in .env don't match your host, check with getent group render video. To
fall back to CPU/software rendering (Mesa llvmpipe), comment out the
devices:, group_add:, and DRINODE/DRI_NODE/PIXELFLUX_WAYLAND lines in
kali-desktop/docker-compose.yml — no other changes needed.
What persists across docker compose up -d --force-recreate:
kali-desktop/config/→ the desktop user's home (/config): dotfiles,~/.config,~/.local, Desktop, Downloads.kali-desktop/opt/→/opt, where many.debpackages (including Obsidian) install their payload.- Your real
/home/jond, bind-mounted read-write at the same path.
Installing a manual .deb app (e.g. Obsidian) so it survives a recreate:
Method A — no root, guaranteed to persist (recommended):
mkdir -p ~/Applications && cd ~/Applications
wget https://github.com/obsidianmd/obsidian-releases/releases/download/v1.13.7/obsidian_1.13.7_amd64.deb
dpkg-deb -x obsidian_1.13.7_amd64.deb obsidian
mkdir -p ~/.local/bin ~/.local/share/applications
ln -sf ~/Applications/obsidian/opt/Obsidian/obsidian ~/.local/bin/obsidian
# copy the app's .desktop file and fix its Exec= line to the symlink above
cp ~/Applications/obsidian/usr/share/applications/*.desktop ~/.local/share/applications/Everything here lives under /config, which is already persisted.
Method B — real apt/dpkg install (integrates with the package manager, but
needs a re-run after recreate):
sudo dpkg -i obsidian_1.13.7_amd64.debThe payload lands in /opt (persisted), but the dpkg database entry and the
/usr/bin symlink/desktop entry are not (only /config and /opt are
volumed). After a container recreate, keep the .deb under ~/Downloads
(persisted) and re-run sudo dpkg -i — it's fast since the /opt payload is
already there, it just relinks.
MIT — see LICENSE.