Skip to content

Security & hardening

LabPod maps each platform account to a Linux account on the host. Rootless Podman runs every workspace as the owner’s Linux user - not as root, not as a shared service account.

What the admin can and cannot do:

Admin canAdmin cannot
View all workspaces and resource usageEnter another user’s terminal session
Force-stop another user’s workspaceStart, edit, delete, or open another user’s workspace
Create and delete usersAccess another user’s files via LabPod

The admin boundary is intentional: force-stop reclaims the container and its reserved resources; anything deeper requires direct host access.

Root cannot own workspaces. The root account cannot create or start its own workspaces because workspace containers must run as a non-root Linux user.

Every workspace container launched by LabPod includes these flags:

FlagWhat it does
--userns=keep-idHost UID = container UID - files have natural ownership
--cap-drop=ALLDrops all Linux capabilities
--security-opt no-new-privilegesPrevents setuid privilege escalation
--initRuns catatonit as PID 1 for zombie reaping
--shm-size <half-of-memory>Prevents PyTorch DataLoader from hitting the 64 MB default
127.0.0.1:<port> bindWorkspace ports bind only to loopback - never directly exposed

Workspace traffic is proxied through LabPod’s reverse proxy, which enforces session-cookie authentication before forwarding. Template launchers use /ws/<id>/<launcher>/; user-declared ports use /ws/<id>/_user/<name>/. The separate namespaces allow a launcher and user port to share a name.

The workspace gateway also confines a web application’s service worker to that application’s URL prefix. A launcher or user-added app cannot register a broad worker that controls another app or workspace on the shared gateway origin.

Containers started directly with Podman are outside the workspace trust boundary. LabPod may inspect them for read-only safety and capacity accounting, but it does not proxy into them or offer launchers, terminals, logs, declared ports, or lifecycle actions. The legacy /ws/c:<container>/ and /ws/_discovered/ forms return 404.

The default systemd unit runs LabPod as root (User=root). Rootless workspaces still run as their owner’s Linux account: the server builds argv arrays and executes Podman as sudo -u <owner> / equivalent user-context commands. It never shells out through bash -c with user input.

By default LabPod serves plain HTTP on port 24680. For HTTPS:

Option 1 - Self-signed certificate (for private LAN use):

Terminal window
# In /etc/labpod/labpod.env:
LABPOD_TLS_SELF_SIGNED=true
LABPOD_TLS_HOSTS=192.168.1.10,labpod.lan # optional extra SANs

On first start, LabPod generates and caches a self-signed cert under /var/lib/labpod/tls/. The cert renews automatically when it nears expiry.

Get the SHA-256 fingerprint for the desktop launcher’s trust-on-first-use pin:

Terminal window
sudo labpod admin tls-fingerprint
Terminal window
# Run these as root
labpod admin tls-fingerprint

Option 2 - Operator-provided certificate:

Terminal window
LABPOD_TLS_CERT=/etc/labpod/tls/server.crt
LABPOD_TLS_KEY=/etc/labpod/tls/server.key

Both LABPOD_TLS_CERT and LABPOD_TLS_KEY must be set together.

On a shared Linux host, enable the loopback port guard so workspace application traffic follows the authenticated LabPod gateway:

Terminal window
# In /etc/labpod/labpod.env:
LABPOD_PORT_GUARD=nft

This installs an nftables rule at startup that restricts direct loopback connections to workspace ports. Normal launcher and user-port links continue to work. The server must run as root (which is the default).

doctor reports whether the port guard is installed correctly:

Terminal window
sudo labpod admin doctor
Terminal window
# Run these as root
labpod admin doctor

If LabPod sits behind a reverse proxy (nginx, Caddy, Cloudflare Tunnel), enable proxy header forwarding:

Terminal window
LABPOD_TRUST_PROXY_HEADERS=true

Only enable this if LabPod is not directly internet-accessible. With this flag off (the default), X-Forwarded-Host and X-Forwarded-Proto from clients are ignored, which is the correct behavior for a direct deployment.

For the WebSocket terminal endpoint, add trusted origins if users connect through a domain name that differs from the server’s address:

Terminal window
LABPOD_WS_ALLOWED_ORIGINS=https://labpod.example.com