Podman
Run Prelude TE under Podman with Quadlet/systemd units or a Kubernetes manifest via podman kube play, including SELinux and rootless notes.
Prelude TE runs unchanged on Podman. There are two supported deployment options:
- Quadlet + systemd
(recommended) - a declarative
.containerunit that systemd manages directly: start at boot, restart policy, journal logging. podman kube play- a Kubernetes Pod manifest played by Podman. The same manifest also deploys to a real Kubernetes cluster, so one file covers both.
What about Compose? The Docker Compose template does run under
podman compose, but the Podman project does not recommend Compose for deployments -podman composeis only a thin wrapper that delegates to an external provider (docker-composeor the communitypodman-compose). Use one of the two options below; if your team standardizes on Compose, run it under Docker as described in Docker.
Prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Podman | 4.4 or later | First version with Quadlet and the podman kube play name. |
| CPU / RAM | 2 vCPU / 2 GB | Same sizing as Docker. |
The image is published to
registry.arolo-solutions.com/prelude/prelude-te and is publicly
pullable — no registry login required:
podman pull registry.arolo-solutions.com/prelude/prelude-te:1.0.0
A standalone deployment publishes two ports: 443 (HTTPS, mapped to
4040 inside the container) and 179 (BGP). Both are privileged
ports, so the examples below run rootful — see
Rootless for the alternative.
Option 1 - Quadlet + systemd (recommended)
Quadlet turns declarative unit files into systemd services at
daemon-reload. Create the storage directory, then place the unit
at /etc/containers/systemd/prelude-te.container:
sudo mkdir -p /var/lib/prelude-te/storage
[Unit]
Description=Prelude Topology Engine
After=network-online.target
Wants=network-online.target
[Container]
ContainerName=prelude-te
Image=registry.arolo-solutions.com/prelude/prelude-te:1.0.0
PublishPort=443:4040
PublishPort=179:179
Volume=/var/lib/prelude-te/storage:/app/storage:Z
Environment=TE_TIMEZONE=Europe/Paris
Environment=TE_LOG_LEVEL=INFO
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
The :Z suffix relabels the bind mount on SELinux-enforcing hosts —
see SELinux and volume mounts. To
serve with your own certificate instead of the auto-generated
self-signed one, place the files under
/var/lib/prelude-te/storage/certs/ and add:
Environment=TE_TLS_CERT=/app/storage/certs/te.crt
Environment=TE_TLS_KEY=/app/storage/certs/te.key
Manage with systemd
# Pick up new unit files — Quadlet generates prelude-te.service
# from prelude-te.container
sudo systemctl daemon-reload
# Start the engine
sudo systemctl start prelude-te
# Status and logs
sudo systemctl status prelude-te
sudo journalctl -u prelude-te -f
Quadlet-generated units are enabled through their [Install]
section — there is no separate systemctl enable step; the unit
starts at boot as soon as it exists.
Updating
Edit Image= in prelude-te.container to the new tag, then:
sudo systemctl daemon-reload
sudo systemctl restart prelude-te
The embedded SQLite database migrates automatically on startup. To
track a moving tag automatically, add AutoUpdate=registry to the
[Container] section and enable the podman-auto-update.timer.
Option 2 - podman kube play
podman kube play runs a Kubernetes manifest directly under
Podman. Save this as /etc/prelude-te/prelude-te.yaml:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prelude-te-storage
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: prelude-te
labels:
app: prelude-te
spec:
containers:
- name: engine
image: registry.arolo-solutions.com/prelude/prelude-te:1.0.0
ports:
- containerPort: 4040
hostPort: 443
- containerPort: 179
hostPort: 179
env:
- name: TE_TIMEZONE
value: Europe/Paris
- name: TE_LOG_LEVEL
value: INFO
volumeMounts:
- name: storage
mountPath: /app/storage
volumes:
- name: storage
persistentVolumeClaim:
claimName: prelude-te-storage
Play and manage
# Bring the pod up
sudo podman kube play /etc/prelude-te/prelude-te.yaml
# Logs
sudo podman pod logs -f prelude-te
# Tear down (the PVC-backed storage volume survives)
sudo podman kube down /etc/prelude-te/prelude-te.yaml
To update, change the image tag in the manifest, then
podman kube down and podman kube play again.
Start at boot with a .kube unit
Quadlet also manages kube manifests, which gives the kube play option the same systemd integration as option 1:
[Kube]
Yaml=/etc/prelude-te/prelude-te.yaml
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start prelude-te
On a Kubernetes cluster
The same manifest deploys to a real cluster with
kubectl apply -f prelude-te.yaml, with two adjustments that
replace the host-specific pieces:
- The two
hostPortentries become Services. The engine terminates TLS itself, so expose4040through a TLS-passthrough Ingress or aLoadBalancerService. The BGP port179must be reachable from your routers at a stable address — aLoadBalancerService with a static IP (orhostNetworkon a pinned node) is the usual answer; a NodePort will not do, since BGP peers are configured with a fixed neighbor address. - Run one replica only. The storage volume holds a SQLite
database and must never be shared between two running instances —
if you wrap the Pod in a Deployment, use the
Recreatestrategy.
To supply your own certificate on a cluster, mount a TLS Secret and
point TE_TLS_CERT / TE_TLS_KEY at it:
kubectl create secret tls prelude-te-tls --cert=te.crt --key=te.key
SELinux and volume mounts
SELinux is enforcing on Fedora, RHEL, and Rocky by default. Bind
mounts need the :Z suffix so Podman relabels them for the
container's SELinux context — the Quadlet unit above already
carries it. Without the label the container fails to write into
storage/ and the SQLite database creation errors out on first
boot.
podman kube play does not take mount suffixes; PVC-backed volumes
(as in the manifest above) are labelled automatically. If you swap
the PVC for a hostPath volume, relabel the directory on the host
instead:
sudo chcon -Rt container_file_t /var/lib/prelude-te/storage
Rootless
Both options also run rootless (units under
~/.config/containers/systemd/, systemctl --user,
loginctl enable-linger $USER to survive logout). The thing to
watch for is the ports: rootless Podman cannot publish ports below
1024 by default, and a standalone deployment publishes both 443
(HTTPS) and 179 (BGP). To publish them rootless, lower the
unprivileged port threshold on the host:
sudo sysctl net.ipv4.ip_unprivileged_port_start=179
Persist it in /etc/sysctl.d/ if you keep the engine rootless, or
run the container rootful and skip the sysctl.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Unit prelude-te.service not found |
File named .service instead of .container — Quadlet ignores it |
Rename to prelude-te.container, then systemctl daemon-reload |
| Unit not found after adding a file | systemd has not regenerated units | systemctl daemon-reload, then check /run/systemd/generator/ |
| Quadlet file rejected | Syntax error in the unit | /usr/libexec/podman/quadlet -dryrun prints the parse errors |
| SQLite errors on first boot | SELinux blocking the storage mount | Add :Z to the Volume= line, or chcon -Rt container_file_t for hostPaths |
bind: permission denied on 443/179 |
Rootless privileged-port limit | Lower ip_unprivileged_port_start or run rootful |
| Routers cannot reach BGP | Port 179 not published or filtered |
Verify PublishPort=179:179 and host firewall rules |