The Containers
A homelab dashboard and self-host deployer: from a docker run command, a Compose file, a folder or a binary.
One app on a home server. It lists the services running there, routes each one through a single Caddy gateway, and deploys new ones.
Before it deploys anything, it reads what you gave it and checks what the host will need. Edit the command below or pick another: The Containers' own parsing and checks run again here, in your browser, on an invented host. Nothing deploys.
Paste a command
The host on this page
An invented Linux machine. The Containers runs there as homelab, from /opt/the-containers. Caddy holds ports 80 and 2019, The Containers 3001, and the demo apps in the screenshots below 18080–18083 and 18090. /srv/scanner and ~/kiosk/site exist; /mnt/nas/archive exists, but homelab can only read it; any other folder doesn't exist yet.
Host permission
This command needs host authorization before deployment can run.
- ready1nothing to do
- auto1it does this itself
- review2needs your OK
- needs fix1yours to fix first
Host port 8080ready
Port 8080 is available.
from
-p 8080:3001Bind mount /srv/pulse/dataauto
/srv/pulse/data does not exist yet. The Containers will create it before Docker starts.
from
-v /srv/pulse/data:/app/dataBind mount /etc/pulseneeds fix
/etc/pulse is in a protected system location. Create it and grant ownership to the user running The Containers first.
sudo mkdir -p '/etc/pulse'sudo chown -R 'homelab' '/etc/pulse'
from
-v /etc/pulse:/app/config:roDocker socket mountreview
/var/run/docker.sock gives the container control over Docker on this host. User confirmation is required.
from
-v /var/run/docker.sock:/var/run/docker.sock:roLinux capabilitiesreview
This command adds capabilities: NET_RAW
from
--cap-add NET_RAW
The plan
- App
- pulse
- Route host
- pulse.homelab.example
- Target
- http://127.0.0.1:8080
- Container
- pulse · ghcr.io/example/pulse:0.8port 3001
- Bind mount paths will be created and checked before Docker starts.
- Reverse proxy mode will bind the host in Caddy; DNS must resolve to this machine for clients to reach it.
The same pulse command, pasted into the real deploy dialog on a scratch database, gives the same five rows in the same order. The desk above follows the app's own code; only the host is invented.

Four ways in
A docker run command is one of the four deploy methods that work today. Each one goes through the same checks before anything runs.
Docker run
Paste the command. It keeps your ports and folders, moves a host port that's taken, or one below 1024, to a free one from 18080 up, and adds the route.
Docker Compose
Paste a Compose file. It picks the service with the web page, here web over db and redis, and routes to it.
Static folder
Point it at a folder that's already built. nginx serves it read-only from a container The Containers manages.
Binary or service
Give it the command that starts a local web service and the port it listens on. It installs it as a user systemd service on Linux or a launchd agent on macOS, and leaves the port as you set it.
Four more are planned, not built: GitHub auto-detect, a Node app, a Python app and a custom command. The deploy dialog shows them marked Next.


One gateway, written for you
The Containers writes the Caddy gateway's config itself, from its list of apps, and loads it through Caddy's admin API. Custom routes go first, then one route per app, then a catch-all back to the dashboard. An app on a sub-path has the prefix stripped before the request is passed on.
Each sync is recorded: success, failed, or skipped while syncing is off.
Caddy, on :80, tries these routes in this order.
- wiki.homelab.exampleredirects (308) to http://notes.homelab.examplea custom route
- pantry.homelab.exampleproxies to 127.0.0.1:18080Pantry
- notes.homelab.exampleproxies to 192.0.2.20:3000Field Notes
- ledger.homelab.exampleproxies to 127.0.0.1:18081Ledger
- photos.homelab.exampleproxies to 127.0.0.1:18082Photo Shelf
- status.homelab.exampleproxies to 127.0.0.1:18083Status Page
- sync.homelab.exampleproxies to 127.0.0.1:18090Sync Hub
- print.homelab.exampleproxies to 192.0.2.31:80Label Printer
- homelab.example/routerstrips /router, proxies to 192.0.2.1:80Router
- pulse.homelab.exampleproxies to 127.0.0.1:8080the command above, not saved
- anything elseproxies to 127.0.0.1:3001the dashboard
Keep watch
The dashboard lists every app with its route, its target and its last health check. Checks run on a schedule you set, or when you ask, and each result is kept.


For the apps it deployed, it compares the saved deployment with what's actually running: a missing or stopped container, a port that moved, a missing Compose file. Where a safe repair exists, it offers it.

A redeploy shows its plan first: what it will do, what it keeps, such as the volumes, the route and the port, and what it removes.

Tests and install
130API tests
They pass on Node's built-in test runner. They include the October security and data-safety fixes: published ports keep their host address and protocol, a failed restore puts the original files back, and a redeploy keeps the container's volumes.
On a Debian or Ubuntu machine it installs with two commands after cloning: ./install.sh, then ./start.sh. A third script, ./deploy/setup-autostart.sh, makes it start on boot.
Limits
Four of the eight deploy methods are planned, not built.
It runs on a machine you own, for a home network. It isn't a hosted service.
It's for Linux and macOS: a binary or service is installed as a user systemd unit on Linux or a launchd agent on macOS.
It won't silently run sudo, or change Docker's socket permissions, DNS records or your router. Those stay with you; the doctor gives the commands where it can.
TypeScript · Fastify and SQLite · React, Vite and Tailwind CSS · Caddy through its admin API · Docker and Docker Compose · user systemd and launchd
MIT license · v0.5.0 · May – October 2026