Install with the pvr CLI
The pvr CLI lets you add a Docker Hub image as a Pantavisor container, commit the change locally, and deploy it to the device over the local network — all without touching the device directly.
Prerequisites: pvr installed on your workstation (install guide) and the device reachable on the local network.
1 — Clone the Device
Clone the device's current revision to your workstation. Pantavisor exposes its state over HTTP on port 12368.
pvr clone http://<device-ip>:12368/cgi-bin mydevice
cd mydevice
The checkout mirrors the live device state — all containers, their rootfs images, LXC configs, and manifests.
2 — Add the New Container
Use pvr app add to pull an image and convert it to a Pantavisor container.
--from isn't limited to Docker Hub — point it at any registry host.
Specify --platform to match your device architecture.
# Docker Hub, ARM64 device (e.g. Raspberry Pi 4, iMX8)
pvr app add tailscale --from tailscale/tailscale --platform linux/arm64
# Docker Hub, ARM32 device (e.g. iMX6)
pvr app add tailscale --from tailscale/tailscale --platform linux/arm/v7
# A private/self-hosted registry
pvr app add api --from registry.example.com/team/api:v2.1 --platform linux/arm64
Pulling from a private registry needs credentials — see Authenticating Against a Private Registry.
pvr app add pulls the image, converts it to a SquashFS rootfs, and creates the container's directory with:
tailscale/
├── root.squashfs ← container filesystem
├── root.squashfs.docker-digest ← image digest for update tracking
├── src.json ← image source (`docker_config`/`docker_digest`, used to detect upstream updates) plus any `--arg KEY=VALUE` template arguments passed to this command
├── run.json ← Pantavisor runtime manifest
└── lxc.container.conf ← LXC runtime configuration
pvr app add reads the image's Docker config and compiles it directly into
lxc.container.conf: ENTRYPOINT/CMD become the container's init command,
ENV becomes lxc.environment, WORKDIR becomes lxc.init.cwd, and
VOLUMEs become mount entries — they carry through unchanged. --arg KEY=VALUE template arguments are separate: they're for values pvr's own
templates read, not overrides of the image's config.
3 — Stage and Commit
Check what was added:
pvr status
Expected output:
A tailscale/lxc.container.conf
A tailscale/root.squashfs
A tailscale/root.squashfs.docker-digest
A tailscale/run.json
A tailscale/src.json
Stage and commit the new revision:
pvr add .
pvr commit -m "add Tailscale container"
4 — Deploy to the Device
Post the new revision to the device's pvr endpoint — the same URL you cloned from:
pvr post http://<device-ip>:12368
Pantavisor downloads the new container objects, writes them as a pending revision, and transitions into it — a full reboot only happens when a system restart-policy container or the BSP changed. If the new revision runs cleanly, it becomes the new permanent state.
5 — Verify
After the transition, confirm the container is running — from the device console (serial, or SSH if
you've set that up) or remotely via pvcontrol:
# From the device console (serial or SSH)
lxc-ls -f
# Or via pvcontrol
pvcontrol container ls
The new container should show as RUNNING. You can also check from the pvtx web UI at http://<device-ip>:12368/app.