Skip to content
Documentation Prelude Collector

You are viewing documentation for 1.0.0. This version is no longer maintained.

See the latest version →

Podman

Run Prelude Collector under Podman with Quadlet/systemd units or a Kubernetes manifest via podman kube play, including SELinux and rootless notes.

Prelude Collector runs unchanged on Podman. Unlike a single-container app, the collector is a three-service stack — PostgreSQL, NATS, and the collector itself — so the examples below bring up all three. There are two supported deployment options:

  1. Quadlet + systemd (recommended) - declarative .container units that systemd manages directly: start at boot, ordering, restart policy, journal logging.
  2. podman kube play - a Kubernetes Pod manifest played by Podman. The same manifest is also the starting point for 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; scale up with subscription count.
Disk ~20 GB free Storage volume, YANG cache, and the PostgreSQL data directory.

The collector image is published to registry.arolo-solutions.com/prelude/prelude-collector and is publicly pullable — no registry login required. The examples below use the :v1.0.0 tag, the current stable release:

podman pull registry.arolo-solutions.com/prelude/prelude-collector:v1.0.0

A standalone deployment publishes the web UI and REST API on 443 (HTTPS, mapped to 4030 inside the container). 443 is a privileged port, so the examples below run rootful — see Rootless for the alternative.

Quadlet turns declarative unit files into systemd services at daemon-reload. The stack is four units — a shared network, PostgreSQL, NATS, and the collector — all placed under /etc/containers/systemd/. Create the data directories first:

sudo mkdir -p /var/lib/prelude-collector/storage
sudo mkdir -p /var/lib/prelude-collector/pgdata

Network

Create /etc/containers/systemd/prelude.network:

[Network]
NetworkName=prelude

PostgreSQL

Then /etc/containers/systemd/prelude-postgres.container:

[Unit]
Description=Prelude Collector - PostgreSQL
After=network-online.target
Wants=network-online.target

[Container]
ContainerName=prelude-postgres
Image=docker.io/library/postgres:16-alpine
Environment=POSTGRES_USER=prelude
Environment=POSTGRES_PASSWORD=change-me-sE43kapqD8df5fds
Environment=POSTGRES_DB=collector
Volume=/var/lib/prelude-collector/pgdata:/var/lib/postgresql/data:Z
Network=prelude.network

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

NATS

Then /etc/containers/systemd/prelude-nats.container:

[Unit]
Description=Prelude Collector - NATS
After=network-online.target
Wants=network-online.target

[Container]
ContainerName=prelude-nats
Image=docker.io/library/nats:2
Network=prelude.network

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

Collector

Finally /etc/containers/systemd/prelude-collector.container:

[Unit]
Description=Prelude Collector
After=prelude-postgres.service prelude-nats.service
Wants=prelude-postgres.service prelude-nats.service

[Container]
ContainerName=prelude-collector
Image=registry.arolo-solutions.com/prelude/prelude-collector:v1.0.0
PublishPort=443:4030
Volume=/var/lib/prelude-collector/storage:/app/storage:Z
Environment=COLLECTOR_DB_HOST=prelude-postgres
Environment=COLLECTOR_DB_PORT=5432
Environment=COLLECTOR_DB_USER=prelude
Environment=COLLECTOR_DB_PASSWORD=change-me-sE43kapqD8df5fds
Environment=COLLECTOR_DB_NAME=collector
Environment=COLLECTOR_APP_URL=https://collector.example.com
Environment=COLLECTOR_TIMEZONE=Europe/Paris
Environment=COLLECTOR_LOG_LEVEL=INFO
Network=prelude.network

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

The :Z suffix relabels the bind mounts on SELinux-enforcing hosts — see SELinux and volume mounts. The COLLECTOR_DB_PASSWORD must match the PostgreSQL unit's POSTGRES_PASSWORD; change both for any non-evaluation deployment. To serve with your own certificate instead of the auto-generated self-signed one, place the files under /var/lib/prelude-collector/storage/certs/ and add:

Environment=COLLECTOR_TLS_CERT=/app/storage/certs/collector.crt
Environment=COLLECTOR_TLS_KEY=/app/storage/certs/collector.key

If you front the collector with a reverse proxy, publish 4030:4030 instead of 443:4030 and terminate TLS at the proxy.

Manage with systemd

# Pick up new unit files — Quadlet generates a .service per .container
sudo systemctl daemon-reload

# Start the stack
sudo systemctl start prelude-postgres prelude-nats prelude-collector

# Status and logs
sudo systemctl status prelude-collector
sudo journalctl -u prelude-collector -f

Quadlet-generated units are enabled through their [Install] section — there is no separate systemctl enable step; they start at boot as soon as the files exist. The collector's After=/Wants= lines order it behind PostgreSQL and NATS, and Restart=always lets it retry until PostgreSQL is accepting connections on the first boot.

First-run setup

On first boot the schema migrates automatically and a default admin (admin / @rolo!Pass246) is seeded. Open the web UI on the published HTTPS port, sign in, and change the password when prompted.

NATS is not configured through an environment variable. Once the stack is up, open Output Settings → NATS in the web UI, set the URL to the NATS container on the shared network, and enable it:

nats://prelude-nats:4222

That single connection powers both data export and the collector's internal signaling (ICMP reachability, OneBoard device-sync). Then issue an API token for the REST API:

sudo podman exec prelude-collector ./prelude-collector user token \
  -u admin -d api-token

Updating

Edit Image= in prelude-collector.container to the new tag, then:

sudo systemctl daemon-reload
sudo systemctl restart prelude-collector

The schema 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. A single Pod holds all three containers; because they share the Pod's network namespace, they reach each other on localhost rather than by container name. Save this as /etc/prelude-collector/prelude-collector.yaml:

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: prelude-collector-storage
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: prelude-collector-pgdata
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: prelude-collector
  labels:
    app: prelude-collector
spec:
  containers:
    - name: postgres
      image: docker.io/library/postgres:16-alpine
      env:
        - name: POSTGRES_USER
          value: prelude
        - name: POSTGRES_PASSWORD
          value: change-me-sE43kapqD8df5fds
        - name: POSTGRES_DB
          value: collector
      volumeMounts:
        - name: pgdata
          mountPath: /var/lib/postgresql/data
    - name: nats
      image: docker.io/library/nats:2
    - name: collector
      image: registry.arolo-solutions.com/prelude/prelude-collector:v1.0.0
      ports:
        - containerPort: 4030
          hostPort: 443
      env:
        - name: COLLECTOR_DB_HOST
          value: localhost
        - name: COLLECTOR_DB_PORT
          value: "5432"
        - name: COLLECTOR_DB_USER
          value: prelude
        - name: COLLECTOR_DB_PASSWORD
          value: change-me-sE43kapqD8df5fds
        - name: COLLECTOR_DB_NAME
          value: collector
        - name: COLLECTOR_APP_URL
          value: https://collector.example.com
        - name: COLLECTOR_TIMEZONE
          value: Europe/Paris
        - name: COLLECTOR_LOG_LEVEL
          value: INFO
      volumeMounts:
        - name: storage
          mountPath: /app/storage
  volumes:
    - name: storage
      persistentVolumeClaim:
        claimName: prelude-collector-storage
    - name: pgdata
      persistentVolumeClaim:
        claimName: prelude-collector-pgdata

Because everything shares one Pod, COLLECTOR_DB_HOST=localhost and the UI NATS URL becomes nats://localhost:4222 — not the container names used in the Quadlet option.

Play and manage

# Bring the pod up
sudo podman kube play /etc/prelude-collector/prelude-collector.yaml

# Logs
sudo podman pod logs -f prelude-collector

# Tear down (the PVC-backed volumes survive)
sudo podman kube down /etc/prelude-collector/prelude-collector.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. Create /etc/containers/systemd/prelude-collector.kube:

[Kube]
Yaml=/etc/prelude-collector/prelude-collector.yaml

[Service]
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start prelude-collector

On a Kubernetes cluster

The manifest is a starting point for a real cluster, with a few adjustments that replace the host-specific pieces:

  • The hostPort becomes a Service. The collector terminates TLS itself, so expose 4030 through a TLS-passthrough Ingress or a LoadBalancer Service.
  • Move COLLECTOR_DB_PASSWORD / POSTGRES_PASSWORD into a Secret rather than inline value: fields.
  • Run the stateful pieces single-replica. The PostgreSQL data directory and the collector storage/ volume must never be shared between two running instances — if you wrap them in a Deployment, use the Recreate strategy, or split PostgreSQL into its own StatefulSet.

SELinux and volume mounts

SELinux is enforcing on AlmaLinux, RHEL, Rocky, and Fedora by default. Bind mounts need the :Z suffix so Podman relabels them for the container's SELinux context — the Quadlet units above already carry it. Without the label the containers cannot write into their volumes and the PostgreSQL data directory or the collector storage/ fails to initialize 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 a PVC for a hostPath volume, relabel the directory on the host instead:

sudo chcon -Rt container_file_t /var/lib/prelude-collector/storage
sudo chcon -Rt container_file_t /var/lib/prelude-collector/pgdata

Rootless

Both options also run rootless (units under ~/.config/containers/systemd/, systemctl --user, loginctl enable-linger $USER to survive logout). The collector's in-container port 4030 is unprivileged, so a rootless deployment that publishes 4030:4030 and sits behind a reverse proxy needs no extra setup. To publish 443 directly as in the examples above, lower the unprivileged port threshold on the host:

sudo sysctl net.ipv4.ip_unprivileged_port_start=443

Persist it in /etc/sysctl.d/ if you keep the stack rootless, or run the containers rootful and skip the sysctl.

Podman vs Docker quick reference

Operation Docker Podman
Start stack docker compose up -d systemctl start prelude-* (Quadlet)
View logs docker compose logs -f journalctl -u prelude-collector -f
Exec into container docker compose exec collector sh podman exec -it prelude-collector sh
Pull images docker compose pull podman pull …
Auto-start at boot Docker daemon enabled Quadlet [Install] section
Rootless by default No Yes
SELinux volume labels Not needed :Z on bind mounts

Troubleshooting

Symptom Likely cause Fix
Unit prelude-collector.service not found File named .service instead of .container — Quadlet ignores it Rename to prelude-collector.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
Collector errors on DB connect at boot PostgreSQL not ready yet, or password mismatch Restart=always retries; confirm COLLECTOR_DB_PASSWORD matches POSTGRES_PASSWORD
Storage / pgdata write errors on first boot SELinux blocking the bind mount Add :Z to the Volume= line, or chcon -Rt container_file_t for hostPaths
No outputs or device reachability data NATS URL not set in the UI Open Output Settings → NATS, set nats://prelude-nats:4222 (or nats://localhost:4222 for kube play), enable it
bind: permission denied on 443 Rootless privileged-port limit Lower ip_unprivileged_port_start, publish 4030:4030, or run rootful

Air-gapped environments

For an air-gapped Podman host, transfer the images with podman save and podman load. See the Air-gapped install tutorial for the step-by-step, and Air-gap deployment for the operational reference.

See also

Filtering by: