Skip to main content
Version: master

Access Your Applications

Pantavisor containers are isolated LXC namespaces. There are three ways to interact with a running application: enter its namespace directly, reach it over the host network, or wire it to other containers through the pv-xconnect service mesh.

Enter a Container with pventer

pventer drops you into a running container's namespace — the embedded equivalent of docker exec. On the device console (serial or SSH):

pventer -c sensor-app

Once inside you have a shell in the container's filesystem, process, and network namespaces. Exit with exit or Ctrl-D to return to the host.

To inspect the container's filesystem without entering:

# Get the container's init PID
lxc-info -n sensor-app -p

# Browse the rootfs through /proc
ls -la /proc/<PID>/root/

Reach a Container over the Host Network

If a container binds to a port, it is directly reachable on the device's IP address — no port mapping or NAT is needed. The host and containers share the default network namespace unless the container's lxc.container.conf explicitly isolates networking.

Example: a container running an HTTP server on port 8080:

curl http://<device-ip>:8080/

To confirm which port a container is listening on, enter its namespace and inspect:

pventer -c my-app
ss -tlnp

Container-to-Container Communication with pv-xconnect

When containers need to communicate with each other without sharing a network namespace, use pv-xconnect. Despite sometimes being called a service mesh, it isn't a sidecar/proxy mesh like Istio or Linkerd — there's no traffic interception or L7 routing. It injects sockets or device nodes directly into a consumer container's namespace, closer to a bind-mount than a network hop.

The provider declares what it exports in services.json:

{
"#spec": "service-manifest-xconnect@1",
"services": [
{"name": "api", "type": "rest", "socket": "/run/myapp/api.sock"}
]
}

The consumer declares what it requires in args.json:

{
"PV_SERVICES_REQUIRED": [
{"name": "api", "target": "/run/pv/services/api.sock"}
]
}

args.json is a build-time input: pvr app add compiles it into the container's run.json (services.required). At runtime Pantavisor reads run.json, not args.json.

Pantavisor's pv-xconnect daemon proxies the connection and injects the socket into the consumer's namespace at /run/pv/services/api.sock. No shared network namespace or port exposure is needed.

To inspect the active service mesh:

pvcontrol graph ls

Supported service types:

TypeUse case
unixRaw Unix domain socket
restHTTP-over-UDS with caller-identity headers
dbusPolicy-aware D-Bus proxy
drmDRM device node injection (card0, renderD128)
waylandWayland compositor access

Remote Access via Tailscale

If the Tailscale container is installed on the device, every container that uses the host network namespace is reachable over the Tailscale mesh network — no router port-forwarding required. Install Tailscale as a regular pvr application; these commands run on your workstation:

pvr clone http://<device-ip>:12368/cgi-bin device && cd device
pvr app add tailscale --from tailscale/tailscale --platform linux/arm64
pvr add . && pvr commit -m "add Tailscale"
pvr post http://<device-ip>:12368

--platform picks the image variant for your device's CPU architecture (see choosing a platform for ARM64 vs ARM32 examples); omit it and pvr will try to infer it from the target.

Authenticating Against a Private Registry

pvr app add/pvr app update resolve registry credentials the same way docker pull does: with no flags, they fall back to your local Docker credential store (~/.docker/config.json, including credential helpers) via the standard OCI default keychain. To pass credentials explicitly instead — useful in CI, where there's no local Docker login — use --username/ --password, or the PVR_REGISTRY_USERNAME/PVR_REGISTRY_PASSWORD environment variables:

pvr app add api --from registry.example.com/team/api:v2.1 \
--username "$PVR_REGISTRY_USERNAME" --password "$PVR_REGISTRY_PASSWORD"

Troubleshooting

SymptomCheck
Container not respondingpvcontrol container ls — verify state is RUNNING
Port unreachable from hostpventer -c <app> then ss -tlnp to confirm the port is bound
xconnect socket missingpvcontrol graph ls — check link is present; pvcontrol daemons ls — confirm pv-xconnect is running
Container crashedtail /pantavisor/logs/<revision>/<app>/lxc/console.log for the exit reason