Podman is RHEL 9’s default container engine and, unlike Docker, runs entirely rootless and integrates natively with systemd. Socket activation is a systemd feature that lets the kernel hold a listening socket open while the service is stopped; the moment the first connection arrives, systemd starts the corresponding service and hands the socket over. This eliminates the need for a long-running container daemon while still guaranteeing the application is available on demand. This tutorial covers the socket activation model, the traditional socket/service unit pair, and the modern Quadlet approach for managing Podman containers through systemd.

Prerequisites

  • RHEL 9 server with a user that has sudo privileges
  • Podman installed (dnf install -y podman)
  • Familiarity with systemd units and basic container concepts
  • Port 8080 available and open in the firewall

Step 1 — Install Podman and Pull a Test Image

Podman is included in the RHEL 9 AppStream repository. Pull a lightweight HTTP server image to use throughout this tutorial.

sudo dnf install -y podman podman-plugins

podman --version
# podman version 4.x.x

# Pull a minimal web server image
podman pull docker.io/library/nginx:alpine

podman images

Step 2 — Understand Socket Activation

The socket activation pattern requires two paired systemd unit files that share the same base name:

  • A .socket unit that tells systemd which address and port to bind and hold open
  • A .service unit that receives the pre-bound socket via the $LISTEN_FDS environment variable when systemd starts it

Verify that the socket activation utility is available, which helps with testing:

which systemd-socket-activate
# /usr/bin/systemd-socket-activate

# Quick local test to see socket activation in action (Ctrl-C to stop)
systemd-socket-activate -l 8080 podman run --rm -p 8080 nginx:alpine &
curl http://localhost:8080
kill %1

Step 3 — Create the systemd Socket Unit

Write the socket unit file. The .socket and .service files must have the same base name for systemd to link them automatically.

sudo tee /etc/systemd/system/myapp.socket <<'EOF'
[Unit]
Description=Socket for myapp container

[Socket]
ListenStream=8080
BindIPv6Only=both

[Install]
WantedBy=sockets.target
EOF

Step 4 — Create the systemd Service Unit

The corresponding service unit starts a Podman container and maps the systemd-held socket into the container. The Type=notify directive allows Podman to signal readiness back to systemd.

sudo tee /etc/systemd/system/myapp.service <<'EOF'
[Unit]
Description=myapp container service
Requires=myapp.socket
After=myapp.socket

[Service]
Type=notify
NotifyAccess=all
ExecStartPre=-/usr/bin/podman rm -f myapp-container
ExecStart=/usr/bin/podman run 
    --name myapp-container 
    --rm 
    -p 8080:80 
    --sdnotify=conmon 
    docker.io/library/nginx:alpine
ExecStop=/usr/bin/podman stop myapp-container
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload

Step 5 — Enable the Socket and Test On-Demand Startup

Enable and start the socket unit only — the service starts automatically on the first incoming connection.

sudo systemctl enable --now myapp.socket
sudo systemctl status myapp.socket
# The service should not be running yet
sudo systemctl status myapp.service

# Send a request — this triggers the service to start
curl http://localhost:8080

# Now verify the container is running
sudo systemctl status myapp.service
podman ps

# View service logs
journalctl -u myapp.service -n 40 --no-pager

# Open the firewall port
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

Step 6 — Use Quadlet for the Modern Recommended Approach

Quadlet, introduced in Podman 4.4 and fully integrated in RHEL 9.3+, lets you define a container in a declarative .container file rather than writing raw systemd unit files. Podman’s systemd generator converts the file into a service unit automatically at boot.

# Quadlet files for system-wide containers live here
sudo mkdir -p /etc/containers/systemd

sudo tee /etc/containers/systemd/myapp.container <<'EOF'
[Unit]
Description=My Nginx container via Quadlet

[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
AutoUpdate=registry

[Service]
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

# Reload the systemd generator to pick up the new .container file
sudo systemctl daemon-reload

# The generator creates myapp.service automatically from the .container file
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service
curl http://localhost:8080

# Enable automatic image updates via a systemd timer
sudo systemctl enable --now podman-auto-update.timer

Conclusion

You have installed Podman on RHEL 9, explored the socket activation model using paired .socket and .service unit files, confirmed that the container starts on demand when traffic arrives, and adopted the Quadlet approach with a declarative .container file that the systemd generator converts into a service automatically. Quadlet is the recommended path for all new Podman-on-systemd workloads on RHEL 9.

Next steps: How to Deploy a .NET Application on RHEL 9, How to Install Go and Build CLI Tools on RHEL 9, and How to Install Java and Configure JAVA_HOME on RHEL 9.