Declarative rootless container workload manager for Linux. Define workloads as TOML files, and workloadctl handles user creation, systemd service generation, volume management, secrets, and container lifecycle — all running as isolated unprivileged users with rootless podman.
Podman Quadlets generate systemd units from container specs — workloadctl goes further by managing the full workload lifecycle:
- Declarative TOML configs instead of systemd unit syntax and raw podman flags
- Automatic user isolation — each workload gets a dedicated system user
(
_wl-<name>) with its own UID, subuid/subgid ranges, and rootless podman instance. Quadlets typically run all containers under a single user. - Lifecycle management — user creation, home/volume directory setup, image transfer, update with automatic rollback, backup/restore, orphan cleanup
- Hardware shortcuts —
--gpu amd,--audio,--input,--virtualizationexpand to the right devices, groups, and mounts - TPM2-encrypted secrets via systemd credentials, with portable export/import
- bootc-ready — drop TOMLs into an image, encrypted secrets safe to commit, everything self-provisions on first boot
Quadlets solve "generate a systemd unit from a container spec." workloadctl solves "declare a workload and have everything from OS user to running container handled automatically."
- Fedora 41+ (or any systemd + podman 5.3+ Linux)
- Python 3.11+
- No bootc or immutable OS required (works on standard Fedora too)
From RPM (local build):
cd workloadctl
rpmbuild -bb --define "_topdir $(pwd)/rpmbuild" \
--define "_sourcedir $(pwd)" rpm/workloadctl.spec
sudo dnf install rpmbuild/RPMS/noarch/workloadctl-*.rpm# Create and start a workload in one command
sudo workloadctl create webserver \
--image docker.io/nginxinc/nginx-unprivileged:alpine \
--ports 8080:8080 \
--enable
# Check status
workloadctl status webserver
# View logs
workloadctl logs webserverOr write a TOML config directly:
sudo tee /etc/workloads.d/webserver.toml <<'EOF'
[workload]
name = "webserver"
image = "docker.io/nginxinc/nginx-unprivileged:alpine"
enabled = true
[network]
mode = "pasta"
ports = ["8080:8080"]
EOF
sudo workloadctl enable webserverEach workload gets:
- A dedicated locked-down system user (
_wl-<name>) - Its own UID/subuid namespace for rootless podman
- A systemd service generated at boot from its TOML config
- Automatic volume directory creation under
/var/lib/workloads/<name>/ - Optional TPM2-encrypted secrets via systemd credentials
At boot, a tiny shell systemd generator emits an early-boot oneshot service
(workload-generate.service) that runs the Python workload-generate script.
The script reads /etc/workloads.d/*.toml and writes per-workload unit files
into /run/systemd/system/ before basic.target is reached. The Python work
is kept out of generator context because systemd expects generators to be
fast and minimal (see systemd.generator(7)) — see docs/workloads.md for
the full architecture.
| Command | Description |
|---|---|
workloadctl create |
Generate a workload TOML config |
workloadctl enable |
Create user, transfer image, start service |
workloadctl disable |
Stop service, optionally purge user/data |
workloadctl status |
Show workload status and resource usage |
workloadctl logs |
View workload container logs |
workloadctl reboot |
Soft-reboot a systemd container (keeps overlay) |
workloadctl recreate |
Recreate container from image (destroys overlay) |
workloadctl update |
Pull new image and recreate |
workloadctl list |
List all configured workloads |
workloadctl secret |
Manage TPM2-encrypted credentials |
Most mutating commands require sudo. Read-only commands work as any user.
- Workload guide — full configuration reference with examples
- CLI reference — complete command documentation
- Secrets management — TPM2-encrypted credentials
- Schema reference — annotated TOML schema
- Example configs — real-world workload definitions
MIT