geoffwilliams@home:~$

Podman Quadlet Services

Kubernetes is great but sometimes you just want to run one container on one VM in the simplest way possible. docker --restart always is one way to do this but its pretty clunky:

  • Container managed by docker not systemd
  • Requires installing docker
  • Completely different to Kubernetes

Podman and Systemd - dream team

Turns out podman has a systemd enabled, kubernetes compatible, service capability called quadlet.

Long story short, if you install podman on RHEL 8+ or Debian Trixie (13)+, you can create a systemd service for a container by defining:

  • Pod - Kubernetes compatible
  • Systemd service - .kube file
  • Volume/Extra files
  • systemctl daemon reload, then enable and start your service with systemd

Worked Example - Nexus

Nexus is a binary repository server for pretty much anything you can think of. I need one in my homelab to ship artifacts to servers and it needs to be outside of Kubernetes so that it can be used for bootstrapping things inside the cluster.

Find an image

First step is to find an image. There’s one from Sonatype at https://hub.docker.com/r/sonatype/nexus3

Test the image a bit with podman run if you like.

Write a pod definition

podman generate kube will generate a Kubernetes compatible pod definition customized for the pod your generating against. I find it easiest to copy paste an existing file when there’s a new service to run

Example:

/etc/containers/systemd/nexus.yml

apiVersion: v1
kind: Pod
metadata:
  annotations:
    io.kubernetes.cri-o.ContainerType/app: container
    io.kubernetes.cri-o.TTY/app: "false"
    io.podman.annotations.autoremove/app: "FALSE"
    io.podman.annotations.init/app: "FALSE"
    io.podman.annotations.privileged/app: "FALSE"
    io.podman.annotations.publish-all/app: "FALSE"
  labels:
    app: nexus
  name: nexus
spec:
  automountServiceAccountToken: false
  containers:
  - image: docker.io/sonatype/nexus3:latest
    name: app
    ports:
    - containerPort: 1234
      hostPort: 1234
    - containerPort: 8081
      hostPort: 8081
    - containerPort: 8443
      hostPort: 8443 
    resources: {}
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_NET_RAW
        - CAP_AUDIT_WRITE
    volumeMounts:
    - mountPath: /nexus-data
      name: nexus-data-vol
  enableServiceLinks: false
  hostname: nexus
  restartPolicy: Never
  volumes:
  - hostPath:
      path: /data/containers/nexus
      type: Directory
    name: nexus-data-vol      
status: {}

Write a systemd service (.kube)

No generate for this one so just copy paste from somewhere:

/etc/containers/systemd/nexus-pod.kube

[Install]
WantedBy=default.target

[Unit]

[Kube]
Yaml=/etc/containers/systemd/nexus.yml
# same MAC address for DHCP
# https://stackoverflow.com/a/78139833
PodmanArgs=--mac-address 2a:6c:0d:5e:a2:8f
PublishPort=8081:8081
PublishPort=1234:1234
PublishPort=8443:8443
Network=podman-vlan-infrastructure

Podman supports macvlan networking. The arguments in the kube file essentially plugin the pod to the network with its own network card and MAC address. Perfect for running network services (podman-vlan-infrastructure was created separately, already).

Volume/Extra files

Last thing to setup is to create the volume listed in the pod definition:

mkdir -p /data/containers/nexus

The directory needs to be accessible by the nexus process which runs as uid 200, gid 200. These are used on the host systems so need to chown 200:200 /data/containers/nexus to directly assign ownership.

Config files will appear in this directory once the service starts and can be directly edited as regular files from the host.

Enable in systemd

systemct daemon-reload will cause podman quadlet to re-evaluate all mappings and map them to systemd services which can then be enabled and started just like any normal service:

systemctl enable nexus-pod
systemctl start nexus-pod

Logs are available with journalctl -u nexus-pod - same as any other service.

Next steps

If you want to run more then a couple of services, its a good idea to automate the above steps. I wrote some quick ansible to do this based on the contents of dict:

role

- name: kube file
  ansible.builtin.copy:
    dest: "/etc/containers/systemd/{{ pod.value.kube_file }}"
    src: "systemd/{{ pod.value.kube_file }}"
    owner: root
    group: root
    mode: '0700'  
  notify: reboot podman 

- name: pod definition file
  ansible.builtin.copy:
    dest: "/etc/containers/systemd/{{ pod.value.pod_definition_file }}"
    src: "systemd/{{ pod.value.pod_definition_file }}"
    owner: root
    group: root
    mode: '0700'
  notify: reboot podman 

- name: "{{ pod.key }} files"
  ansible.builtin.file:
    path: "{{ item.path }}"
    state: "{{ item.state }}"
    owner: "{{ item.owner }}"
    group: "{{ item.group }}"
    mode: "{{ item.mode }}"
  loop: "{{ pod.value.files | default([]) }}"
  notify: reboot podman

- name: "{{ pod.key }} cp"
  ansible.builtin.copy:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    owner: "{{ item.owner }}"
    group: "{{ item.group }}"
    mode: "{{ item.mode }}"
  loop: "{{ pod.value.cp | default([]) }}"
  notify: reboot podman

- name: "{{ pod.key }} service"
  ansible.builtin.systemd_service:
    state: started
    daemon_reload: true
    name: "{{ pod.key }}"
    enabled: true

Host variables

quadlet_services:
  nexus-pod:
    kube_file: nexus-pod.kube 
    pod_definition_file: nexus.yml
    files:
    - path: /data/containers/nexus
      state: directory   
      owner: 200
      group: 200
      mode: '0700'

Summary

I can’t think of a better way to run containerized services outside of Kubernetes in 2025. Having a kubernetes compatible pod definition means you have a solid starting point to migrate a service to Kubernetes proper in the future too, if needed.

The long version

Take a look at Repeatable container deployments with Podman in Linux - Into the Terminal 132 from Nate Lager at Red Hat for the gory details.

Post comment

Markdown is allowed, HTML is not. All comments are moderated.