Container + systemd
Quick Reference
# Generate systemd unit from container (Podman)
podman generate systemd --new --name myapp > ~/.config/systemd/user/container-myapp.service
# Quadlet (Podman 4.4+) - declarative container unit
# ~/.config/containers/systemd/myapp.container
[Container]
Image=nginx:alpine
PublishPort=8080:80
# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now container-myapp.service
# View logs
journalctl --user -u container-myapp.service -f
Container Service Models
Why systemd Integration?
Running containers as systemd services provides:
-
Automatic startup - Containers start on boot
-
Restart policies - Automatic recovery from failures
-
Dependency management - Start order, dependencies on other services
-
Logging - Integrated with journald
-
Resource control - cgroups integration
-
Security - User services, sandboxing options
Integration Methods
| Method | Description | Best For |
|---|---|---|
podman generate systemd |
Generate unit file from running container |
Quick migration |
Quadlet |
Declarative container definition |
New deployments (Podman 4.4+) |
Manual unit files |
Hand-crafted systemd units |
Custom requirements |
Docker Compose + systemd |
Compose as systemd service |
Docker users |
Podman Generate Systemd
Generate Unit Files
# First, create container how you want it
podman run -d --name web -p 8080:80 nginx:alpine
# Generate unit file
podman generate systemd --name web
# Generate with --new flag (recreates container each start)
podman generate systemd --new --name web
# Save to user systemd directory
mkdir -p ~/.config/systemd/user
podman generate systemd --new --name web > ~/.config/systemd/user/container-web.service
# Save to system directory (root)
sudo podman generate systemd --new --name web > /etc/systemd/system/container-web.service
Generate Options
# Specify restart policy
podman generate systemd --new --restart-policy=always --name web
# Set restart timeout
podman generate systemd --new --restart-sec=10 --name web
# Custom container name in unit
podman generate systemd --new --container-prefix=app --name web
# Creates: container-app-web.service
# Time to wait for container stop
podman generate systemd --new --stop-timeout=30 --name web
# Include after dependencies
podman generate systemd --new --after=network-online.target --name web
Enable and Manage
# Reload systemd
systemctl --user daemon-reload
# Enable (start on login)
systemctl --user enable container-web.service
# Start now
systemctl --user start container-web.service
# Enable and start
systemctl --user enable --now container-web.service
# Check status
systemctl --user status container-web.service
# View logs
journalctl --user -u container-web.service
journalctl --user -u container-web.service -f # Follow
# Stop
systemctl --user stop container-web.service
# Disable
systemctl --user disable container-web.service
User Services and Lingering
# User services stop when user logs out
# Enable lingering to keep services running
loginctl enable-linger $USER
# Verify
loginctl show-user $USER | grep Linger
# Disable
loginctl disable-linger $USER
# Check user service directory
ls ~/.config/systemd/user/
# User runtime directory
echo $XDG_RUNTIME_DIR
# Usually /run/user/UID
Quadlet (Podman 4.4+)
Quadlet provides declarative container definitions that systemd manages directly.
Container Definition
# ~/.config/containers/systemd/nginx.container
# or /etc/containers/systemd/nginx.container (system-wide)
[Container]
Image=docker.io/library/nginx:alpine
ContainerName=nginx
# Port mapping
PublishPort=8080:80
# Volume mounts
Volume=/var/www/html:/usr/share/nginx/html:ro,Z
# Environment
Environment=NGINX_HOST=localhost
# Resource limits
PodmanArgs=--memory 512m --cpus 1
[Service]
Restart=always
[Install]
WantedBy=default.target
Available Quadlet Types
| Extension | Purpose |
|---|---|
.container |
Single container |
.volume |
Named volume |
.network |
Container network |
.kube |
Kubernetes YAML |
.image |
Image pull/build |
.pod |
Pod definition |
Volume Definition
# ~/.config/containers/systemd/data.volume
[Volume]
# Creates: systemd-data volume
Label=app=myapp
[Install]
WantedBy=default.target
Network Definition
# ~/.config/containers/systemd/app-network.network
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
Label=app=myapp
[Install]
WantedBy=default.target
Using Quadlet
# Place files in quadlet directory
mkdir -p ~/.config/containers/systemd/
# Create container definition
cat > ~/.config/containers/systemd/web.container << 'EOF'
[Container]
Image=nginx:alpine
PublishPort=8080:80
[Service]
Restart=always
[Install]
WantedBy=default.target
EOF
# Generate and reload
systemctl --user daemon-reload
# List generated units
systemctl --user list-unit-files | grep web
# Start
systemctl --user start web.service
# Enable
systemctl --user enable web.service
Quadlet Container Options
[Container]
# Image and name
Image=docker.io/library/nginx:alpine
ContainerName=web
# Network
PublishPort=8080:80
PublishPort=8443:443
Network=host
Network=app-network.network
# Volumes
Volume=/host/path:/container/path:ro
Volume=data.volume:/var/lib/data
Mount=type=tmpfs,destination=/tmp
# Environment
Environment=KEY=value
EnvironmentFile=/path/to/env
Secret=mysecret,type=env,target=API_KEY
# User
User=1000
Group=1000
UserNS=keep-id
# Security
ReadOnly=true
NoNewPrivileges=true
DropCapability=ALL
AddCapability=NET_BIND_SERVICE
SecurityLabelType=container_t
# Health check
HealthCmd=/usr/bin/curl -f http://localhost/health
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
# Entrypoint and command
Exec=/usr/bin/nginx -g "daemon off;"
# Labels and annotations
Label=app=web
Annotation=io.containers.autoupdate=registry
# Additional podman args
PodmanArgs=--memory 512m
Manual Unit Files
Basic Container Unit
# /etc/systemd/system/container-web.service
# or ~/.config/systemd/user/container-web.service
[Unit]
Description=Nginx Web Container
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=10
# Container lifecycle
ExecStartPre=-/usr/bin/podman rm -f web
ExecStart=/usr/bin/podman run --rm --name web -p 8080:80 nginx:alpine
ExecStop=/usr/bin/podman stop -t 10 web
[Install]
WantedBy=multi-user.target
With Dependencies
# container-app.service
[Unit]
Description=Application Container
After=network-online.target container-db.service
Requires=container-db.service
Wants=network-online.target
[Service]
Type=simple
Restart=always
ExecStartPre=-/usr/bin/podman rm -f app
ExecStart=/usr/bin/podman run --rm --name app \
--network=app-net \
-e DATABASE_URL=postgres://db:5432/app \
myapp:latest
ExecStop=/usr/bin/podman stop -t 30 app
[Install]
WantedBy=multi-user.target
Rootless User Service
# ~/.config/systemd/user/container-web.service
[Unit]
Description=Web Container (Rootless)
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/podman run --rm --name web -p 8080:80 nginx:alpine
ExecStop=/usr/bin/podman stop -t 10 web
# User slice for cgroups
Slice=user.slice
[Install]
WantedBy=default.target
Docker with systemd
Docker Compose as Service
# /etc/systemd/system/docker-compose-app.service
[Unit]
Description=Docker Compose Application
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
Individual Docker Container
# /etc/systemd/system/docker-web.service
[Unit]
Description=Web Container (Docker)
Requires=docker.service
After=docker.service
[Service]
Type=simple
Restart=always
ExecStartPre=-/usr/bin/docker rm -f web
ExecStart=/usr/bin/docker run --rm --name web -p 8080:80 nginx:alpine
ExecStop=/usr/bin/docker stop -t 10 web
[Install]
WantedBy=multi-user.target
Auto-Update Containers
Podman Auto-Update
# Label container for auto-update
podman run -d --name web \
--label "io.containers.autoupdate=registry" \
nginx:alpine
# Generate unit with auto-update support
podman generate systemd --new --name web
# Enable auto-update timer
systemctl enable --now podman-auto-update.timer
# Check timer
systemctl list-timers podman-auto-update
# Manual update
podman auto-update
# Dry run
podman auto-update --dry-run
Logging and Monitoring
Journald Integration
# Container logs go to journald by default
# View logs
journalctl --user -u container-web.service
journalctl -u container-web.service # System service
# Follow logs
journalctl --user -u container-web.service -f
# Since boot
journalctl --user -u container-web.service -b
# Last hour
journalctl --user -u container-web.service --since "1 hour ago"
# Export logs
journalctl --user -u container-web.service -o json > logs.json
Troubleshooting
Service Won’t Start
# Check service status
systemctl --user status container-web.service
# View recent logs
journalctl --user -u container-web.service -n 50
# Check unit file syntax
systemd-analyze verify ~/.config/systemd/user/container-web.service
# Check if container exists
podman ps -a | grep web
# Try running container manually
podman run --rm nginx:alpine
Dependency Issues
# List dependencies
systemctl --user list-dependencies container-web.service
# Check ordering
systemctl --user show container-web.service | grep -E "After|Before|Requires"
# Verify dependent services
systemctl --user status network-online.target
User Service Issues
# Check lingering
loginctl show-user $USER | grep Linger
# Enable lingering
loginctl enable-linger $USER
# Check XDG_RUNTIME_DIR
echo $XDG_RUNTIME_DIR
ls -la $XDG_RUNTIME_DIR
# User daemon status
systemctl --user status
Quadlet Issues
# Check quadlet generator
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun
# Verify files in correct location
ls ~/.config/containers/systemd/
# Check for syntax errors
cat ~/.config/containers/systemd/web.container
# Reload and check
systemctl --user daemon-reload
systemctl --user list-unit-files | grep web
Quick Command Reference
# Generate unit (Podman)
podman generate systemd --new --name CONTAINER
# Quadlet location
~/.config/containers/systemd/ # User
/etc/containers/systemd/ # System
# User service management
systemctl --user daemon-reload
systemctl --user enable --now SERVICE
systemctl --user status SERVICE
journalctl --user -u SERVICE -f
# System service management
sudo systemctl daemon-reload
sudo systemctl enable --now SERVICE
journalctl -u SERVICE -f
# Enable lingering
loginctl enable-linger $USER
# Auto-update
podman auto-update
systemctl enable --now podman-auto-update.timer