1276 words
6 minutes
Systemd Service Setup for Podman and Docker Containers - Production-Ready Guide

Systemd Service Setup for Podman and Docker Containers#

Managing containerized applications in production requires robust service management that ensures containers start automatically, restart on failure, and integrate seamlessly with the host system. This guide provides production-ready systemd service configurations for both Podman and Docker containers.

Understanding Systemd Integration#

Systemd provides powerful service management capabilities that complement container orchestration. By creating systemd unit files for containers, you gain:

  • Automatic container startup at boot
  • Dependency management between services
  • Automatic restart on failure
  • Resource control and limits
  • Centralized logging via journald
  • Integration with system monitoring tools
graph TD
A[System Boot] --> B[systemd init]
B --> C[Load Unit Files]
C --> D[Resolve Dependencies]
D --> E[Start Container Services]
E --> F[Monitor Health]
F --> G{Container Healthy?}
G -->|Yes| H[Continue Running]
G -->|No| I[Restart Container]
I --> F
H --> J[System Shutdown]
J --> K[Stop Containers Gracefully]

Basic Systemd Service for Docker#

Simple Docker Container Service#

Create a basic systemd service file for a Docker container:

/etc/systemd/system/myapp-docker.service
[Unit]
Description=MyApp Docker Container
Documentation=https://docs.myapp.com
Wants=network-online.target docker.service
After=network-online.target docker.service
[Service]
Type=simple
Restart=always
RestartSec=5
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker stop myapp
ExecStartPre=-/usr/bin/docker rm myapp
ExecStart=/usr/bin/docker run \
--name myapp \
--rm \
-p 8080:8080 \
-v /data/myapp:/data \
-e APP_ENV=production \
myapp:latest
ExecStop=/usr/bin/docker stop myapp
[Install]
WantedBy=multi-user.target

Docker with Health Checks#

For production environments, implement health checking:

/etc/systemd/system/webapp-docker.service
[Unit]
Description=Web Application Docker Container
Documentation=https://docs.webapp.com
Wants=network-online.target docker.service
After=network-online.target docker.service
Before=nginx.service
[Service]
Type=notify
NotifyAccess=all
Restart=on-failure
RestartSec=10
StartLimitBurst=3
StartLimitIntervalSec=60
# Pull latest image before starting
ExecStartPre=/usr/bin/docker pull webapp:latest
# Remove any existing container
ExecStartPre=-/usr/bin/docker stop webapp
ExecStartPre=-/usr/bin/docker rm webapp
# Start container with health check
ExecStart=/usr/bin/docker run \
--name webapp \
--rm \
--health-cmd="curl -f http://localhost:8080/health || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-retries=3 \
--health-start-period=40s \
-p 8080:8080 \
-v /var/lib/webapp:/data \
-e DATABASE_URL=postgresql://db:5432/webapp \
--memory="1g" \
--cpus="0.5" \
webapp:latest
# Graceful shutdown
ExecStop=/usr/bin/docker stop -t 30 webapp
# Post-stop cleanup
ExecStopPost=-/usr/bin/docker rm -f webapp
[Install]
WantedBy=multi-user.target

Podman Systemd Services#

Basic Podman Service#

Podman integrates more naturally with systemd, supporting rootless containers:

# ~/.config/systemd/user/myapp-podman.service (for rootless)
# /etc/systemd/system/myapp-podman.service (for root)
[Unit]
Description=MyApp Podman Container
Documentation=https://docs.myapp.com
Wants=network-online.target
After=network-online.target
[Service]
Type=forking
Restart=always
RestartSec=5
TimeoutStartSec=30
Environment="PODMAN_SYSTEMD_UNIT=%n"
KillMode=mixed
# Create and start container
ExecStartPre=/usr/bin/podman create \
--name myapp \
--replace \
-p 8080:8080 \
-v myapp-data:/data:Z \
-e APP_ENV=production \
--health-cmd="curl -f http://localhost:8080/health || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-retries=3 \
myapp:latest
ExecStart=/usr/bin/podman start myapp
ExecStop=/usr/bin/podman stop -t 30 myapp
ExecStopPost=/usr/bin/podman rm -f myapp
[Install]
WantedBy=default.target # For user services
# WantedBy=multi-user.target # For system services

Podman with Systemd Mode#

Podman can run containers with systemd as PID 1:

/etc/systemd/system/systemd-container.service
[Unit]
Description=Systemd Container with Podman
Documentation=https://docs.example.com
After=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=10
TimeoutStartSec=0
# Run container with systemd mode
ExecStart=/usr/bin/podman run \
--name systemd-container \
--rm \
--systemd=true \
--cap-add=SYS_ADMIN \
-v /sys/fs/cgroup:/sys/fs/cgroup:ro \
-v systemd-data:/data:Z \
-p 8080:8080 \
fedora-systemd:latest
ExecStop=/usr/bin/podman stop -t 60 systemd-container
[Install]
WantedBy=multi-user.target

Podman Generate Systemd#

Podman can automatically generate systemd unit files:

Generate from Running Container#

Terminal window
# Start a container
podman run -d \
--name nginx \
-p 80:80 \
-v nginx-data:/usr/share/nginx/html:Z \
nginx:alpine
# Generate systemd unit
podman generate systemd \
--new \
--name \
--files \
nginx
# Move generated file to systemd directory
sudo mv container-nginx.service /etc/systemd/system/

Generate for Pod#

Terminal window
# Create a pod
podman pod create \
--name webapp \
-p 8080:8080 \
-p 5432:5432
# Add containers to pod
podman run -d \
--pod webapp \
--name webapp-app \
myapp:latest
podman run -d \
--pod webapp \
--name webapp-db \
postgres:13
# Generate systemd units for pod
podman generate systemd \
--new \
--files \
--pod-prefix="" \
--container-prefix="" \
--separator="-" \
webapp

Advanced Service Configurations#

Service with Dependencies#

/etc/systemd/system/app-stack.service
[Unit]
Description=Application Stack Container
Documentation=https://docs.app.com
Wants=network-online.target postgresql.service redis.service
After=network-online.target postgresql.service redis.service
Before=nginx.service
Requires=postgresql.service redis.service
[Service]
Type=simple
Restart=on-failure
RestartSec=10
StartLimitBurst=5
StartLimitIntervalSec=300
# Environment variables
Environment="NODE_ENV=production"
EnvironmentFile=-/etc/sysconfig/app-stack
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
TasksMax=4096
MemoryLimit=2G
CPUQuota=200%
# Security settings
NoNewPrivileges=true
PrivateTmp=true
# Container execution
ExecStartPre=-/usr/bin/podman stop app-stack
ExecStartPre=-/usr/bin/podman rm app-stack
ExecStart=/usr/bin/podman run \
--name app-stack \
--rm \
--network=host \
--env-file=/etc/app-stack/env \
--secret=app-secret,type=env,target=APP_SECRET \
--memory=2g \
--cpus=2 \
--pids-limit=1000 \
--ulimit nofile=65536:65536 \
app-stack:latest
ExecStop=/usr/bin/podman stop -t 60 app-stack
# Cleanup on failure
ExecStopPost=-/usr/bin/sh -c 'podman rm -f app-stack || true'
[Install]
WantedBy=multi-user.target

Multi-Container Application#

# /etc/systemd/system/webapp@.service
[Unit]
Description=WebApp Container - %i
Documentation=https://docs.webapp.com
PartOf=webapp.target
After=network-online.target webapp-init.service
Requires=webapp-init.service
[Service]
Type=simple
Restart=always
RestartSec=5
Environment="INSTANCE=%i"
# Read instance-specific configuration
EnvironmentFile=/etc/webapp/%i.env
ExecStartPre=-/usr/bin/podman stop webapp-%i
ExecStartPre=-/usr/bin/podman rm webapp-%i
ExecStart=/usr/bin/podman run \
--name webapp-%i \
--rm \
--network=webapp-net \
--env-file=/etc/webapp/%i.env \
-v webapp-%i-data:/data:Z \
--label="com.example.webapp.instance=%i" \
webapp:latest
ExecStop=/usr/bin/podman stop webapp-%i
[Install]
WantedBy=webapp.target

Podman Quadlets (Systemd Integration)#

Podman 4.4+ introduces Quadlets for native systemd integration:

Container Quadlet#

/etc/containers/systemd/webapp.container
[Unit]
Description=Web Application Container
After=network-online.target
[Container]
Image=docker.io/myapp/webapp:latest
PublishPort=8080:8080
Volume=webapp-data:/data:Z
Environment=APP_ENV=production
Environment=LOG_LEVEL=info
Label=app=webapp
Label=version=1.0
# Health check
HealthCmd=curl -f http://localhost:8080/health
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
HealthStartPeriod=40s
# Resource limits
Memory=1g
CPUQuota=50%
# Security
NoNewPrivileges=true
ReadOnly=true
ReadOnlyTmpfs=true
[Service]
Restart=always
RestartSec=10
TimeoutStartSec=30
[Install]
WantedBy=multi-user.target

Pod Quadlet#

/etc/containers/systemd/webapp.pod
[Unit]
Description=Web Application Pod
After=network-online.target
[Pod]
PodName=webapp-pod
PublishPort=8080:8080
PublishPort=9090:9090
Network=webapp-net
Volume=shared-data:/shared:Z
[Service]
Restart=always
[Install]
WantedBy=multi-user.target

Service Management Commands#

Basic Operations#

Terminal window
# Reload systemd after creating/modifying unit files
sudo systemctl daemon-reload
# Start service
sudo systemctl start myapp.service
# Enable service to start at boot
sudo systemctl enable myapp.service
# Start and enable in one command
sudo systemctl enable --now myapp.service
# Check service status
sudo systemctl status myapp.service
# View service logs
sudo journalctl -u myapp.service -f
# Restart service
sudo systemctl restart myapp.service
# Stop service
sudo systemctl stop myapp.service

Troubleshooting#

Terminal window
# View detailed service information
systemctl show myapp.service
# Check why service failed
systemctl status myapp.service
journalctl -xe -u myapp.service
# List all failed services
systemctl list-units --failed
# Reset failed state
sudo systemctl reset-failed myapp.service
# View service dependencies
systemctl list-dependencies myapp.service
# Analyze boot time impact
systemd-analyze blame | grep myapp

Best Practices#

1. Container Lifecycle Management#

graph LR
A[Pre-Start] --> B[Health Check]
B --> C[Start Container]
C --> D[Post-Start Validation]
D --> E[Running State]
E --> F[Health Monitoring]
F --> G{Healthy?}
G -->|Yes| E
G -->|No| H[Restart Logic]
H --> B
E --> I[Graceful Shutdown]
I --> J[Cleanup]

2. Restart Policies#

# Production-ready restart configuration
[Service]
# Restart on any non-zero exit code
Restart=on-failure
# Wait before restarting
RestartSec=10
# Limit restart attempts
StartLimitBurst=5
StartLimitIntervalSec=300
# What to do when start limit is hit
StartLimitAction=reboot # or 'none', 'reboot-force'

3. Resource Management#

[Service]
# Memory limits
MemoryAccounting=true
MemoryMax=2G
MemoryHigh=1.5G
# CPU limits
CPUAccounting=true
CPUQuota=200% # 2 cores
CPUWeight=100
# I/O limits
IOAccounting=true
IOWeight=100
IOReadBandwidthMax=/dev/sda 10M
IOWriteBandwidthMax=/dev/sda 10M
# Task limits
TasksAccounting=true
TasksMax=512

4. Security Hardening#

[Service]
# Run as non-root user
User=webapp
Group=webapp
# Filesystem protection
ReadOnlyPaths=/
ReadWritePaths=/var/lib/webapp
TemporaryFileSystem=/tmp:rw,nosuid,nodev,noexec,size=100M
PrivateTmp=true
# System call filtering
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# Capability restrictions
NoNewPrivileges=true
CapabilityBoundingSet=
AmbientCapabilities=
# Network isolation (if not needed)
PrivateNetwork=true
# Device access
PrivateDevices=true
DevicePolicy=closed

Monitoring and Alerting#

Systemd Service Monitoring#

/usr/local/bin/check-container-services.sh
#!/bin/bash
SERVICES=(
"webapp-docker.service"
"database-podman.service"
"cache-redis.service"
)
for service in "${SERVICES[@]}"; do
if ! systemctl is-active --quiet "$service"; then
echo "CRITICAL: $service is not running"
# Send alert (email, Slack, etc.)
systemctl status "$service" | mail -s "Service $service failed" ops@example.com
fi
done

Prometheus Integration#

# prometheus-systemd-exporter.service
[Unit]
Description=Prometheus Systemd Exporter
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/podman run \
--name systemd-exporter \
--rm \
--pid=host \
-v /sys/fs/cgroup:/host/sys/fs/cgroup:ro \
-v /run/systemd:/run/systemd:ro \
-p 9558:9558 \
quay.io/prometheus/systemd-exporter:latest \
--systemd.collector.unit-whitelist="(webapp|database|cache).*\.service"
[Install]
WantedBy=multi-user.target

Migration Strategies#

Docker Compose to Systemd#

#!/bin/bash
# Convert docker-compose.yml to systemd services
# Example: Convert a simple service
SERVICE_NAME="webapp"
IMAGE="myapp:latest"
PORT="8080:8080"
VOLUME="/data/webapp:/app/data"
cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF
[Unit]
Description=${SERVICE_NAME} Container
After=docker.service
Requires=docker.service
[Service]
Type=simple
Restart=always
ExecStartPre=-/usr/bin/docker stop ${SERVICE_NAME}
ExecStartPre=-/usr/bin/docker rm ${SERVICE_NAME}
ExecStart=/usr/bin/docker run \\
--name ${SERVICE_NAME} \\
--rm \\
-p ${PORT} \\
-v ${VOLUME} \\
${IMAGE}
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now ${SERVICE_NAME}.service

Conclusion#

Systemd provides a robust foundation for managing containerized applications in production. By leveraging systemd’s features with Docker and Podman, you can create reliable, self-healing container deployments that integrate seamlessly with your Linux infrastructure. Whether using traditional unit files or Podman’s Quadlets, the key is to implement proper health checking, resource management, and security controls to ensure your containers run reliably in production environments.

Resources#

Systemd Service Setup for Podman and Docker Containers - Production-Ready Guide
https://mranv.pages.dev/posts/systemd-service-podman-docker-containers/
Author
Anubhav Gain
Published at
2025-01-29
License
CC BY-NC-SA 4.0