A Docker-based Web UI for managing Jailmaker “jail-like containers” powered by systemd-nspawn on TrueNAS SCALE.
This project is a convenience layer around the upstream Jailmaker workflow and concepts documented here (usage section):
https://github.com/Jip-Hop/jailmaker?tab=readme-ov-file#usage
TrueNAS SCALE is Linux-based, and systemd-nspawn can run lightweight, OS-level environments (often called “jails” in TrueNAS/FreeBSD parlance). These environments behave like containers but feel closer to small VMs:
- Each jail has its own root filesystem and userspace.
- You can start/stop them like services.
- You can bind-mount datasets into them.
- They are ideal for running services in a clean, isolated userspace while still integrating well with the host storage layout.
Jailmaker (upstream) provides a CLI (jlmkr.py) that creates and manages these nspawn-based “jails” using an on-disk layout such as:
<baseDir>/jails/<name>/...
jlmkr-webui aims to make Jailmaker usable from a browser on TrueNAS SCALE:
- Provides a web dashboard to list, create, start/stop/restart/remove jails.
- Supports multi-root setups (multiple datasets / base directories).
- Provides streamed logs for long-running operations (create/backup/restore).
- Adds quality-of-life behaviors (e.g., keeping jail running-state during backup; starting jail after restore).
- Works well with TrueNAS SCALE by running inside a privileged Docker container that can execute Jailmaker commands on the host via
nsenter.
The Web UI is designed to make day-to-day container operations (create/start/stop/shell/logs/config) simple, while still keeping the underlying workflow transparent and fully usable from the command line.
On TrueNAS SCALE, the recommended mode is:
- Container runs with:
privileged: truepid: host- mounts
/runfor DBus and basic host access
- Commands are executed on the host using:
nsenter -t 1 ... chroot /proc/1/root ...
- Jailmaker (
jlmkr.py) lives on your datasets, and this UI calls it.
Security note
This container can run host-level commands. Treat it as a trusted admin tool. Put it behind your LAN or a reverse proxy with strong authentication.
- TrueNAS SCALE with Apps/Docker support
- Datasets mounted under
/mnt/... - A place to store UI state (recommended dataset), e.g.:
/mnt/Data/jailmaker-webui-state
- Jailmaker base directories (one per “root”), e.g.:
/mnt/applications/jailmaker/mnt/backup/alina/jailmaker- etc.
Below is a working example docker-compose.yml for TrueNAS SCALE using network_mode: host.
Important
- Use strong secrets for
JWT_SECRETandPASSWORD_RESET_CODE.- Ensure you mount each root path you want to manage into the container (rw).
- Ensure you mount your state dataset to
/data.
services:
jlmkr-webui:
image: kosztyk/jlmkr-webui:latest
container_name: jlmkr-webui
restart: unless-stopped
user: "0:0"
privileged: true
pid: host
# Easiest on TrueNAS; alternatively publish ports (see note below)
network_mode: host
environment:
# Web UI
PORT: "3000"
# Set these to strong secrets
JWT_SECRET: "changeme"
PASSWORD_RESET_CODE: "changeme"
STATE_DIR: "/data"
JLMKR_DATA_DIRS: "/mnt/tank/path where jails will be saved/jailmaker"
JLMKR_SCRIPT_PATHS: "/mnt/tank/path where jails will be saved/jailmaker/jlmkr.py"
JLMKR_EXEC_MODE: "nsenter"
volumes:
- "/mnt/backup/alina/jailmaker:/mnt/backup/alina/jailmaker:rw"
- "/mnt/backup/test:/mnt/backup/test:rw"
- "/mnt/immichp/jailmaker:/mnt/immichp/jailmaker:rw"
- "/mnt/applications/jailmaker:/mnt/applications/jailmaker:rw"
- "/mnt/iCloud\\ sync/immich/jailmaker:/mnt/iCloud\\ sync/immich/jailmaker:rw"
- "/mnt/Data/Backups/jails:/mnt/Data/Backups/jails:rw"
- "/mnt/Data/jailmaker-webui-state:/data:rw"
- /run:/run:rw
- /sys/fs/cgroup:/sys/fs/cgroup:rwWith network_mode: host and PORT=3000, browse to:
http://<truenas-ip>:3000
On the login screen, use the bootstrap workflow (only possible when no users exist yet). After bootstrap, log in normally.
A “root” is a Jailmaker base directory such as:
/mnt/applications/jailmaker
Each root should contain:
/mnt/.../jailmaker/jlmkr.py/mnt/.../jailmaker/jails/(created by Jailmaker as needed)
The UI supports multi-root, so you can keep different jails on different datasets.
Purpose: View and control existing jails across all configured roots.
What you can do:
- List jails (aggregated from enabled roots)
- Start / Stop / Restart
- Remove (delete jail files)
- Open Shell (interactive terminal via host execution)
- Refresh the list
Notes:
- If no roots are configured, you’ll see a banner prompting you to configure Installation.
Purpose: Create a new jail (streamed output).
Key fields:
- Root selector (where the jail will be created)
- Jail name
- Distro / Release (from Jailmaker images index)
- Additional options (start after create, binds, etc.)
- Clear button resets the whole form
Optional feature:
- Install Docker checkbox:
- If checked, the UI injects a first-boot setup script that installs Docker + Docker Compose inside the jail (best-effort, distro-dependent).
Notes:
- Creation runs as a streamed task; you’ll see progress and the final exit code.
Purpose: Backup and restore jail directories using streamed tasks.
- Choose jail
- Choose Backup root (or Auto-detect if available)
- Backup is created as a
.tar.gzarchive in the configured backups location.
Running-state behavior:
- If the jail was running before backup, the app stops it for consistency and starts it again after backup.
- Choose backup archive
- Choose Restore target root (this is important in multi-root setups)
- Extracts into
<targetRoot>/jails/<name> - After restore, the app attempts to start the jail
Purpose: Configure and verify roots.
What happens when you add a new path:
- App validates / normalizes it into a Jailmaker root.
- (If enabled in your build) it can:
- ensure
<path>/jailmakerexists - ensure
<path>/jailmaker/jlmkr.pyexists - download
jlmkr.pyfrom upstream if missing chmod +x jlmkr.py
- ensure
Verify:
- “Verify all” checks:
- path exists
- script exists
jlmkr.py imagesruns successfully in TrueNAS nsenter mode
Purpose: Switch between dark/light mode.
- Theme preference is stored locally (browser) so the UI stays consistent across sessions.
- Check container logs:
docker logs --tail 200 jlmkr-webuiCommon causes:
- syntax error in server.js (container will crash-loop)
- root resolution mismatch (invalid rootId)
- missing host mounts (
/run, datasets not mounted rw)
Usually means you’re targeting the wrong root.
- Confirm jail exists on disk:
ls -la /mnt/<dataset>/jailmaker/jails- In UI, explicitly pick the correct Backup root / Restore target root.
If you host behind a reverse proxy using a subpath, ensure the UI is using relative API paths (recommended). If you see requests going to /api/... instead of ./api/..., fix your base path handling.
This Web UI (Docker + nsenter TrueNAS model) is one option. You may also be interested in:
Similar GUI using SFTP connection:
https://github.com/Kosztyk/TrueNas-Scale-Jailmaker-GUI
Bare-metal Ubuntu/Debian approach:
https://github.com/Kosztyk/-Jail-like-Linux-containers-with-systemd-nspawn
Upstream Jailmaker:
https://github.com/Jip-Hop/jailmaker
- Jailmaker concept and
jlmkr.pyoriginate from the upstream Jailmaker project. - This Web UI is an independent interface layer that executes upstream commands and follows upstream semantics.