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:
[Unit]Description=MyApp Docker ContainerDocumentation=https://docs.myapp.comWants=network-online.target docker.serviceAfter=network-online.target docker.service
[Service]Type=simpleRestart=alwaysRestartSec=5TimeoutStartSec=0ExecStartPre=-/usr/bin/docker stop myappExecStartPre=-/usr/bin/docker rm myappExecStart=/usr/bin/docker run \ --name myapp \ --rm \ -p 8080:8080 \ -v /data/myapp:/data \ -e APP_ENV=production \ myapp:latestExecStop=/usr/bin/docker stop myapp
[Install]WantedBy=multi-user.target
Docker with Health Checks
For production environments, implement health checking:
[Unit]Description=Web Application Docker ContainerDocumentation=https://docs.webapp.comWants=network-online.target docker.serviceAfter=network-online.target docker.serviceBefore=nginx.service
[Service]Type=notifyNotifyAccess=allRestart=on-failureRestartSec=10StartLimitBurst=3StartLimitIntervalSec=60
# Pull latest image before startingExecStartPre=/usr/bin/docker pull webapp:latest
# Remove any existing containerExecStartPre=-/usr/bin/docker stop webappExecStartPre=-/usr/bin/docker rm webapp
# Start container with health checkExecStart=/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 shutdownExecStop=/usr/bin/docker stop -t 30 webapp
# Post-stop cleanupExecStopPost=-/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 ContainerDocumentation=https://docs.myapp.comWants=network-online.targetAfter=network-online.target
[Service]Type=forkingRestart=alwaysRestartSec=5TimeoutStartSec=30Environment="PODMAN_SYSTEMD_UNIT=%n"KillMode=mixed
# Create and start containerExecStartPre=/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 myappExecStop=/usr/bin/podman stop -t 30 myappExecStopPost=/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:
[Unit]Description=Systemd Container with PodmanDocumentation=https://docs.example.comAfter=network-online.target
[Service]Type=simpleRestart=alwaysRestartSec=10TimeoutStartSec=0
# Run container with systemd modeExecStart=/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
# Start a containerpodman run -d \ --name nginx \ -p 80:80 \ -v nginx-data:/usr/share/nginx/html:Z \ nginx:alpine
# Generate systemd unitpodman generate systemd \ --new \ --name \ --files \ nginx
# Move generated file to systemd directorysudo mv container-nginx.service /etc/systemd/system/
Generate for Pod
# Create a podpodman pod create \ --name webapp \ -p 8080:8080 \ -p 5432:5432
# Add containers to podpodman run -d \ --pod webapp \ --name webapp-app \ myapp:latest
podman run -d \ --pod webapp \ --name webapp-db \ postgres:13
# Generate systemd units for podpodman generate systemd \ --new \ --files \ --pod-prefix="" \ --container-prefix="" \ --separator="-" \ webapp
Advanced Service Configurations
Service with Dependencies
[Unit]Description=Application Stack ContainerDocumentation=https://docs.app.comWants=network-online.target postgresql.service redis.serviceAfter=network-online.target postgresql.service redis.serviceBefore=nginx.serviceRequires=postgresql.service redis.service
[Service]Type=simpleRestart=on-failureRestartSec=10StartLimitBurst=5StartLimitIntervalSec=300
# Environment variablesEnvironment="NODE_ENV=production"EnvironmentFile=-/etc/sysconfig/app-stack
# Resource limitsLimitNOFILE=65536LimitNPROC=4096TasksMax=4096MemoryLimit=2GCPUQuota=200%
# Security settingsNoNewPrivileges=truePrivateTmp=true
# Container executionExecStartPre=-/usr/bin/podman stop app-stackExecStartPre=-/usr/bin/podman rm app-stackExecStart=/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 failureExecStopPost=-/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 - %iDocumentation=https://docs.webapp.comPartOf=webapp.targetAfter=network-online.target webapp-init.serviceRequires=webapp-init.service
[Service]Type=simpleRestart=alwaysRestartSec=5Environment="INSTANCE=%i"
# Read instance-specific configurationEnvironmentFile=/etc/webapp/%i.env
ExecStartPre=-/usr/bin/podman stop webapp-%iExecStartPre=-/usr/bin/podman rm webapp-%iExecStart=/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
[Unit]Description=Web Application ContainerAfter=network-online.target
[Container]Image=docker.io/myapp/webapp:latestPublishPort=8080:8080Volume=webapp-data:/data:ZEnvironment=APP_ENV=productionEnvironment=LOG_LEVEL=infoLabel=app=webappLabel=version=1.0
# Health checkHealthCmd=curl -f http://localhost:8080/healthHealthInterval=30sHealthTimeout=10sHealthRetries=3HealthStartPeriod=40s
# Resource limitsMemory=1gCPUQuota=50%
# SecurityNoNewPrivileges=trueReadOnly=trueReadOnlyTmpfs=true
[Service]Restart=alwaysRestartSec=10TimeoutStartSec=30
[Install]WantedBy=multi-user.target
Pod Quadlet
[Unit]Description=Web Application PodAfter=network-online.target
[Pod]PodName=webapp-podPublishPort=8080:8080PublishPort=9090:9090Network=webapp-netVolume=shared-data:/shared:Z
[Service]Restart=always
[Install]WantedBy=multi-user.target
Service Management Commands
Basic Operations
# Reload systemd after creating/modifying unit filessudo systemctl daemon-reload
# Start servicesudo systemctl start myapp.service
# Enable service to start at bootsudo systemctl enable myapp.service
# Start and enable in one commandsudo systemctl enable --now myapp.service
# Check service statussudo systemctl status myapp.service
# View service logssudo journalctl -u myapp.service -f
# Restart servicesudo systemctl restart myapp.service
# Stop servicesudo systemctl stop myapp.service
Troubleshooting
# View detailed service informationsystemctl show myapp.service
# Check why service failedsystemctl status myapp.servicejournalctl -xe -u myapp.service
# List all failed servicessystemctl list-units --failed
# Reset failed statesudo systemctl reset-failed myapp.service
# View service dependenciessystemctl list-dependencies myapp.service
# Analyze boot time impactsystemd-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 codeRestart=on-failure
# Wait before restartingRestartSec=10
# Limit restart attemptsStartLimitBurst=5StartLimitIntervalSec=300
# What to do when start limit is hitStartLimitAction=reboot # or 'none', 'reboot-force'
3. Resource Management
[Service]# Memory limitsMemoryAccounting=trueMemoryMax=2GMemoryHigh=1.5G
# CPU limitsCPUAccounting=trueCPUQuota=200% # 2 coresCPUWeight=100
# I/O limitsIOAccounting=trueIOWeight=100IOReadBandwidthMax=/dev/sda 10MIOWriteBandwidthMax=/dev/sda 10M
# Task limitsTasksAccounting=trueTasksMax=512
4. Security Hardening
[Service]# Run as non-root userUser=webappGroup=webapp
# Filesystem protectionReadOnlyPaths=/ReadWritePaths=/var/lib/webappTemporaryFileSystem=/tmp:rw,nosuid,nodev,noexec,size=100MPrivateTmp=true
# System call filteringSystemCallFilter=@system-serviceSystemCallErrorNumber=EPERM
# Capability restrictionsNoNewPrivileges=trueCapabilityBoundingSet=AmbientCapabilities=
# Network isolation (if not needed)PrivateNetwork=true
# Device accessPrivateDevices=trueDevicePolicy=closed
Monitoring and Alerting
Systemd Service Monitoring
#!/bin/bashSERVICES=( "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 fidone
Prometheus Integration
# prometheus-systemd-exporter.service[Unit]Description=Prometheus Systemd ExporterAfter=network-online.target
[Service]Type=simpleExecStart=/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 serviceSERVICE_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} ContainerAfter=docker.serviceRequires=docker.service
[Service]Type=simpleRestart=alwaysExecStartPre=-/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.targetEOF
systemctl daemon-reloadsystemctl 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.