Skip to content

Running cang in a container

Cang is published as an OCI image and can be run with Podman, Docker, or any OCI-compatible runtime.

registry.marvin8.zone/marvin8/cang:latest

The image ships two binaries — cangcang (ingest daemon) and cangjian (web server). Both share one image; the container command selects which to run.

Architecture

Cang runs as two containers sharing a pod (Podman) or a shared network (Docker Compose). Both need the same volume mounts:

Container path What to mount Mode
/config/cang.toml Your cang.toml config file :ro
/data Output directory for snapshots and cang.db read-write

On top of that, cangcang needs each camera root mounted at the same path declared in cang.toml. cangjian does not need camera mounts — it only reads the database and snapshot store.

Quick start with Docker Compose

Create a minimal cang.toml (see configuration reference):

[server]
output_dir = "/data"

[[camera]]
name = "front-door"
adapter = "dahua"
root = "/cameras/ABC123456"

Note: camera roots inside the container use /cameras/… rather than the host path. Mount them accordingly in the compose file.

services:
  cangcang:
    image: registry.marvin8.zone/marvin8/cang:latest
    command: cangcang /config/cang.toml
    environment:
      - TZ=Australia/Brisbane
    volumes:
      - ./cang.toml:/config/cang.toml:ro
      - cang-data:/data
      - /mnt/nvr/ABC123456:/cameras/ABC123456:ro
    restart: unless-stopped

  cangjian:
    image: registry.marvin8.zone/marvin8/cang:latest
    command: cangjian /config/cang.toml
    environment:
      - TZ=Australia/Brisbane
    ports:
      - "8080:8080"
    volumes:
      - ./cang.toml:/config/cang.toml:ro
      - cang-data:/data
    restart: unless-stopped

volumes:
  cang-data:

The web UI will be available at http://localhost:8080/days.

Systemd Quadlet setup (Podman pod)

Quadlet is the recommended way to run Podman containers as persistent systemd services without root.

Cang uses a pod to share the network namespace between the two containers and optionally a Tailscale sidecar. Pod-level volumes are inherited by every container.

cang.pod

[Pod]
PodName=cang-pod
Volume=/home/NAS/cang/cang.toml:/config/cang.toml:ro
Volume=/home/NAS/cang/clips:/data
ExitPolicy=stop

cangcang.container

[Container]
Pod=cang.pod
ContainerName=cangcang
Image=registry.marvin8.zone/marvin8/cang:latest
Environment=TZ=Australia/Brisbane
Volume=/mnt/nvr/ABC123456:/cameras/ABC123456:ro
Exec=cangcang /config/cang.toml
UserNS=keep-id

[Service]
Restart=on-failure
TimeoutStopSec=70

cangjian.container

[Container]
Pod=cang.pod
ContainerName=cangjian
Image=registry.marvin8.zone/marvin8/cang:latest
Environment=TZ=Australia/Brisbane
Exec=cangjian /config/cang.toml
UserNS=keep-id

[Service]
Restart=on-failure
TimeoutStopSec=70

Place the files in ~/.config/containers/systemd/, then activate:

systemctl --user daemon-reload
systemctl --user enable --now cang-pod

To start automatically at boot without a logged-in session:

loginctl enable-linger

The ,z SELinux relabel suffix on volume mounts is required on SELinux-enforcing systems (Fedora, RHEL, etc.); omit it on systems without SELinux.

Building locally

podman build -t cang .

Run the ingest daemon:

podman run --rm \
  -v ./cang.toml:/config/cang.toml:ro \
  -v /tmp/cang-data:/data \
  -v /mnt/nvr/ABC123456:/cameras/ABC123456:ro \
  cang cangcang /config/cang.toml

Run the web server in a separate terminal:

podman run --rm \
  -v ./cang.toml:/config/cang.toml:ro \
  -v /tmp/cang-data:/data \
  -p 8080:8080 \
  cang cangjian /config/cang.toml