Skip to content
GitHubLinkedIn

Containers as a Service

This page explains how to run long-lived containers as rootless systemd user services using Podman Quadlet.

This is the default deployment model for application backends documented under:

Exceptions (not containerized by default):

  • Services restart automatically (systemd handles lifecycle).
  • Containers run without root privileges.
  • Deployments are file-based and auditable (.container unit files).
  • Podman installed on the host.
  • systemd user sessions enabled (loginctl enable-linger).
  • If SELinux is enabled (RHEL/Alma/Rocky), use :Z on bind mounts where appropriate.

Create a dedicated service user (one-time)

Section titled “Create a dedicated service user (one-time)”

Run as root:

useradd -m -s /bin/bash <service_user>
passwd <service_user>
loginctl enable-linger <service_user>

Optional (if you want to read system logs):

usermod -aG systemd-journal <service_user>

As the service user, create a unit in:

~/.config/containers/systemd/

Example:

[Unit]
Description=My internal service
After=network-online.target local-fs.target

[Container]
ContainerName=myservice
Image=docker.io/your/image:latest
Network=slirp4netns:allow_host_loopback=true
PublishPort=8080:80
Volume=/home/<service_user>/myservice/config:/etc/myservice/config:Z
Volume=/home/<service_user>/myservice/data:/var/lib/myservice:Z
Environment=MY_ENV_VAR=value

[Service]
Restart=always
TimeoutStartSec=900

[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now myservice.service
systemctl --user status myservice.service

Systemd unit logs:

journalctl --user -u myservice.service -f

Application logs (container):

podman ps
podman logs myservice

Validate a unit file (path may vary per distro):

/usr/libexec/podman/quadlet -dryrun -user ~/.config/containers/systemd/myservice.container
  • Bind mounts must use full paths; relative paths are not supported.
  • Services won’t start after reboot if linger isn’t enabled for the service user.
  • Port binds may fail if the port is already in use or blocked by policy.