Skip to content
Documentation Prelude Topology Engine 1.0.0

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:

  1. Quadlet + systemd (recommended) - a declarative .container unit that systemd manages directly: start at boot, restart policy, journal logging.
  2. 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 compose is only a thin wrapper that delegates to an external provider (docker-compose or the community podman-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.

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 hostPort entries become Services. The engine terminates TLS itself, so expose 4040 through a TLS-passthrough Ingress or a LoadBalancer Service. The BGP port 179 must be reachable from your routers at a stable address — a LoadBalancer Service with a static IP (or hostNetwork on 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 Recreate strategy.

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
Filtering by: