Skip to main content

Install with Docker Compose

This is the full, step-by-step install path. If you just want the fast path, see the Quickstart. If you're setting up with an AI coding agent instead, see Install with an AI agent, which follows the same steps with an added Verify check after each one.

1. Get the repository and generate secrets

git clone https://github.com/badbread/crumbvms.git crumb && cd crumb
./scripts/setup-env.sh

scripts/setup-env.sh writes a gitignored .env with strong, randomly generated secrets (a Postgres password, a JWT signing secret, and the go2rtc restreamer's Basic-auth credentials). It refuses to overwrite an existing .env unless you pass --force. You do not need to set an admin password here; you'll create the admin account in the browser during first run. If you want to set one anyway for a headless/scripted install, run ./scripts/setup-env.sh --prompt.

Don't hand-edit the generated secrets, and don't commit .env, it stays gitignored by design.

2. Choose where recordings are stored

By default, .env points MEDIA_HOST_PATH at ./_data next to the repo. For anything beyond a quick trial, point it at a disk with real headroom:

MEDIA_HOST_PATH=/mnt/your-disk/crumb-data

Make sure the directory exists and is writable. You can add more disks later without touching the compose file: mount them under this same host path (or a subdirectory) and add the storage path in the admin console.

3. Bring up the stack

docker compose pull
docker compose up -d
docker compose ps

docker compose pull fetches prebuilt api and recorder images. If that fails with "not found" or a permission-denied error, the images aren't published for the repository or fork you're running yet, use the build-from-source override instead:

docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build

The base compose file requires GO2RTC_USER and GO2RTC_PASS to be set. If you generated .env with setup-env.sh this is already handled; if you hand-edited .env from .env.example and left those blank, docker compose up will fail fast with a clear "variable is not set" error rather than booting insecurely. Re-run scripts/setup-env.sh instead of inventing values.

Verify: docker compose ps shows every service running (or healthy), and:

curl -fsS http://localhost:8080/health

returns 200 OK. A 503 for the first few seconds is normal while Postgres and migrations finish; retry.

What's running

ServicePortReachable fromPurpose
api8080LANAdmin console + REST API, plain HTTP
caddy8443 (default)LANSame API over HTTPS, self-signed by default
recorder18554LANRTSP restream for native clients
recorder8556 (tcp+udp)LANWebRTC media for live view
postgresnoneinternal onlynot published to the host

There is no separate go2rtc container: Crumb's restreamer runs embedded inside the recorder process. Its own REST API is not published to the LAN at all, only reachable from the api container over the internal Docker network.

Two things also run automatically with no extra steps: a nightly Postgres backup (see Backups) and the database migrations that bring a fresh Postgres up to the current schema.

4. Finish setup in the browser

Open http://<host-lan-ip>:8080/admin. A first-run wizard walks you through accepting the tester terms, creating your administrator account, confirming the server's address, choosing storage and retention, and finding cameras on your network. See First-run wizard for the full walkthrough.

Optional: HTTPS, hardware decode, remote access

  • HTTPS is already running by default at https://<host>:8443 with a self-signed certificate; see TLS for what the browser warning means and how to trust or replace the certificate.
  • Hardware-accelerated motion decode is opt-in; see Hardware decode.
  • Remote access should go through a private overlay like Tailscale or WireGuard, not port-forwarding. The default install is LAN-only on purpose; see the ground rules in Install with an AI agent for why, and don't expose Crumb to the public internet without TLS and a strong admin password already in place.

Stopping the stack

docker compose down

This is the full stop, the kill switch. Your data (media files, the Postgres volume) is untouched; docker compose up -d brings it back the way you left it.