Skip to main content

The Containers

A homelab dashboard and self-host deployer: from a docker run command, a Compose file, a folder or a binary.

Open source · 2026View on GitHub

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:3001

  • Bind 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/data

  • Bind 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:ro

  • Docker 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:ro

  • Linux 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.

The real deploy dialog's command requirements for the pulse command: Host port 8080 ready, Bind mount /srv/pulse/data auto, Bind mount /etc/pulse needs fix with sudo mkdir and sudo chown, Docker socket mount review, Linux capabilities review.
The real app's command requirements for pulse.

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.

  1. 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.

  2. Docker Compose

    Paste a Compose file. It picks the service with the web page, here web over db and redis, and routes to it.

  3. Static folder

    Point it at a folder that's already built. nginx serves it read-only from a container The Containers manages.

  4. 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.

  5. 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.

The deploy dialog's Compose file field holding three services: web, db and redis.
The filled-in plan: app name bookclub, proxy host bookclub.homelab.example, container bookclub, image ghcr.io/example/bookclub:3.0, route target and the Prepared line.
Docker Compose in the real deploy dialog: three services pasted, and the plan it filled in. An invented demo app; Deploy wasn't pressed.

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.

  1. wiki.homelab.exampleredirects (308) to http://notes.homelab.examplea custom route
  2. pantry.homelab.exampleproxies to 127.0.0.1:18080Pantry
  3. notes.homelab.exampleproxies to 192.0.2.20:3000Field Notes
  4. ledger.homelab.exampleproxies to 127.0.0.1:18081Ledger
  5. photos.homelab.exampleproxies to 127.0.0.1:18082Photo Shelf
  6. status.homelab.exampleproxies to 127.0.0.1:18083Status Page
  7. sync.homelab.exampleproxies to 127.0.0.1:18090Sync Hub
  8. print.homelab.exampleproxies to 192.0.2.31:80Label Printer
  9. homelab.example/routerstrips /router, proxies to 192.0.2.1:80Router
  10. pulse.homelab.exampleproxies to 127.0.0.1:8080the command above, not saved
  11. anything elseproxies to 127.0.0.1:3001the dashboard
The screenshots' demo apps, put through the same rules here. It matches the config the app generated for them.

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.

The Containers' dashboard: eight invented apps in cards, each with its route host, target and last health check; Photo Shelf's check failed.
The same dashboard at phone width, with the Dashboard, Admin and Deploy tabs at the bottom.
The dashboard with eight invented apps; Photo Shelf's check is failing. Beside it, the same at phone width.

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.

Photo Shelf's deployment panel: the container has exited, the drift check warns on container state, and the repair action is Start container.
Photo Shelf's container has stopped, so the repair is Start container. The stopped container and its history are seeded on a scratch database.

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.

The redeploy preview for photo-shelf, listing its actions, what it preserves and what it removes.
The redeploy preview for Photo Shelf. Redeploy wasn't pressed.

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

  1. Four of the eight deploy methods are planned, not built.

  2. It runs on a machine you own, for a home network. It isn't a hosted service.

  3. 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.

  4. 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