Table of Contents
Overview
Podman Quadlet revolutionizes container management by providing deep systemd integration. It automatically generates systemd units from simple declarative files, enabling containers to be managed as native system services with all the benefits of systemd’s powerful service management capabilities.
Understanding Podman Quadlet
Architecture Overview
graph TB subgraph "Quadlet System" A[Quadlet Files] --> B[Quadlet Generator] B --> C[systemd Units] C --> D[Podman] D --> E[Containers] end
subgraph "File Types" F[.container] --> A G[.volume] --> A H[.network] --> A I[.kube] --> A J[.pod] --> A end
subgraph "systemd Integration" C --> K[Service Management] C --> L[Dependency Resolution] C --> M[Resource Control] C --> N[Logging] end
style B fill:#4ecdc4,stroke:#087f5b,stroke-width:2px style C fill:#74c0fc,stroke:#1971c2,stroke-width:2px style D fill:#ffd43b,stroke:#fab005,stroke-width:2px
Key Benefits
- Native systemd integration: Containers run as systemd services
- Automatic unit generation: No manual systemd unit writing
- Dependency management: Define container dependencies declaratively
- Resource control: Leverage systemd’s cgroup management
- Automatic updates: Built-in container image update support
- Boot integration: Containers start automatically at system boot
Installation and Setup
Prerequisites
# Check Podman version (requires 4.4+)podman --version
# On Fedora/RHEL/CentOSsudo dnf install -y podman
# On Ubuntu/Debian (22.04+)sudo apt updatesudo apt install -y podman
# Verify systemd integrationsystemctl --version
Directory Structure
# System-wide Quadlet files/etc/containers/systemd/
# User-specific Quadlet files~/.config/containers/systemd/
# Create directoriesmkdir -p ~/.config/containers/systemdsudo mkdir -p /etc/containers/systemd
Basic Container Management
Simple Container Example
Create ~/.config/containers/systemd/nginx.container
:
[Unit]Description=Nginx Web ServerAfter=network-online.target
[Container]Image=docker.io/library/nginx:alpineContainerName=nginxPublishPort=8080:80Volume=/var/www/html:/usr/share/nginx/html:roEnvironment=NGINX_HOST=localhostEnvironment=NGINX_PORT=80
[Service]Restart=alwaysTimeoutStartSec=900
[Install]WantedBy=default.target
Managing the Container
# Reload systemd to detect new Quadlet filessystemctl --user daemon-reload
# Start the containersystemctl --user start nginx.service
# Enable auto-start at bootsystemctl --user enable nginx.service
# Check statussystemctl --user status nginx.service
# View logsjournalctl --user -u nginx.service -f
Advanced Container Configuration
Container with Health Checks
Create healthcheck-app.container
:
[Unit]Description=Application with Health CheckAfter=network-online.target
[Container]Image=docker.io/myapp:latestContainerName=healthcheck-appPublishPort=3000:3000
# Health check configurationHealthCmd="/usr/bin/curl -f http://localhost:3000/health || exit 1"HealthInterval=30sHealthRetries=3HealthStartPeriod=60sHealthTimeout=10s
# Resource limitsMemoryLimit=512MCPUQuota=50%
# Security optionsReadOnly=trueNoNewPrivileges=trueSeccompProfile=/etc/containers/seccomp.json
[Service]Restart=on-failureRestartSec=30
[Install]WantedBy=multi-user.target
Multi-Container Application
graph TD subgraph "Application Stack" A[Database Container] --> B[App Container] B --> C[Proxy Container] D[Cache Container] --> B end
subgraph "Quadlet Files" E[postgres.container] F[app.container] G[nginx-proxy.container] H[redis.container] end
subgraph "Shared Resources" I[app-network] J[db-data.volume] K[cache-data.volume] end
E --> A F --> B G --> C H --> D
A --> J D --> K A --> I B --> I C --> I D --> I
style B fill:#4ecdc4,stroke:#087f5b,stroke-width:2px style I fill:#74c0fc,stroke:#1971c2,stroke-width:2px
Volume Management
Creating Named Volumes
Create app-data.volume
:
[Volume]VolumeName=app-dataLabels=app=myapp,environment=productionOptions=nodev,noexec
[Install]WantedBy=multi-user.target
Using Volumes in Containers
[Container]Image=docker.io/postgres:15Volume=postgres-data.volume:/var/lib/postgresql/data:ZVolume=/backup:/backup:ro
Volume Backup Container
Create backup.container
:
[Unit]Description=Volume Backup ServiceAfter=postgres.service
[Container]Image=docker.io/alpine:latestContainerName=backupVolume=postgres-data.volume:/data:roVolume=/backup:/backup:rwExec=/bin/sh -c "tar czf /backup/postgres-$(date +%Y%m%d).tar.gz -C /data ."
# Run as one-shotRunInit=true
[Service]Type=oneshotRemainAfterExit=false
[Install]WantedBy=default.target
Network Configuration
Custom Network Definition
Create app-network.network
:
[Network]NetworkName=app-networkSubnet=10.88.0.0/16Gateway=10.88.0.1DNS=10.88.0.1DNS=8.8.8.8Label=app=myappLabel=environment=production
[Install]WantedBy=multi-user.target
Container Network Configuration
[Container]Image=docker.io/myapp:latestNetwork=app-network.networkIP=10.88.0.10HostName=myapp.local
# Multiple networksNetwork=app-network.networkNetwork=backend-network.network
# Host networkingNetwork=host
Pod Management
Creating a Pod
Create webapp.pod
:
[Unit]Description=Web Application Pod
[Pod]PodName=webappNetwork=app-network.networkPublishPort=8080:80PublishPort=8443:443
[Service]Restart=on-failure
[Install]WantedBy=default.target
Pod Containers
Create webapp-frontend.container
:
[Unit]Description=Frontend ContainerAfter=webapp.pod
[Container]Image=docker.io/frontend:latestPod=webapp.podEnvironment=BACKEND_URL=http://localhost:3000
[Service]Restart=always
[Install]WantedBy=default.target
Kubernetes YAML Support
Deploy Kubernetes Manifests
Create app-deployment.kube
:
[Unit]Description=Kubernetes Application DeploymentAfter=network-online.target
[Kube]Yaml=/etc/containers/k8s/app-deployment.yamlNetwork=podmanConfigMap=/etc/containers/k8s/config.yamlPodmanArgs=--log-level=info
[Service]Restart=on-failureTimeoutStartSec=600
[Install]WantedBy=default.target
Kubernetes YAML Example
apiVersion: v1kind: Podmetadata: name: myappspec: containers: - name: app image: docker.io/myapp:latest ports: - containerPort: 8080 env: - name: DATABASE_URL value: postgres://localhost:5432/mydb - name: postgres image: docker.io/postgres:15 env: - name: POSTGRES_DB value: mydb - name: POSTGRES_PASSWORD value: secret
Auto-Update Configuration
Enabling Auto-Updates
[Container]Image=docker.io/myapp:latestAutoUpdate=registryLabel=io.containers.autoupdate=registry
[Service]Restart=always
Auto-Update Service
# Enable auto-update timersystemctl --user enable --now podman-auto-update.timer
# Check timer statussystemctl --user status podman-auto-update.timer
# Manually trigger updatesystemctl --user start podman-auto-update.service
# View update logsjournalctl --user -u podman-auto-update.service
Update Policy Configuration
graph TD A[Auto-Update Policies] --> B[registry] A --> C[local] A --> D[disabled]
B --> E[Pull from Registry] C --> F[Use Local Image] D --> G[No Updates]
E --> H[Restart Container] F --> I[Rollback on Failure]
style A fill:#4ecdc4,stroke:#087f5b,stroke-width:2px style B fill:#74c0fc,stroke:#1971c2,stroke-width:2px style I fill:#ffd43b,stroke:#fab005,stroke-width:2px
Service Dependencies
Complex Dependency Chain
Create database.container
:
[Unit]Description=PostgreSQL DatabaseBefore=app.service
[Container]Image=docker.io/postgres:15ContainerName=postgresNetwork=app-network.network
[Service]Restart=always
[Install]WantedBy=multi-user.target
Create app.container
:
[Unit]Description=Application ServerAfter=database.serviceRequires=database.serviceAfter=redis.serviceWants=redis.service
[Container]Image=docker.io/myapp:latestContainerName=appNetwork=app-network.networkEnvironment=DATABASE_URL=postgres://postgres@database:5432/mydbEnvironment=REDIS_URL=redis://redis:6379
[Service]Restart=on-failureRestartSec=10
[Install]WantedBy=default.target
Resource Management
CPU and Memory Limits
[Container]Image=docker.io/myapp:latest
# Memory limitsMemoryLimit=2GMemoryReservation=1GMemorySwap=4G
# CPU limitsCPUQuota=200%CPUShares=1024CPUSet=0-3
# I/O limitsIOReadBandwidthMax=/dev/sda:10MIOWriteBandwidthMax=/dev/sda:10M
# Process limitsPidsLimit=100
Resource Monitoring
# Monitor container resourcespodman stats nginx
# System-wide resource usagesystemctl --user status nginx.service
# Detailed cgroup informationsystemctl --user show nginx.service | grep -E "(Memory|CPU|IO)"
# Resource usage over timejournalctl --user -u nginx.service | grep -i resource
Security Configuration
Security Options
[Container]Image=docker.io/secure-app:latest
# User and groupUser=1000:1000UserNS=keep-id
# CapabilitiesCapAdd=NET_ADMINCapDrop=ALL
# Security labelsSecurityLabelType=spc_tSecurityLabelLevel=s0:c123,c456
# Read-only root filesystemReadOnly=trueReadOnlyTmpfs=true
# Seccomp profileSeccompProfile=/etc/containers/seccomp-profile.json
# AppArmor profileApparmorProfile=container-default
# No new privilegesNoNewPrivileges=true
SELinux Integration
# Check SELinux contextls -Z ~/.config/containers/systemd/
# Set correct contextchcon -t container_file_t ~/.config/containers/systemd/*.container
# Verify container SELinux labelspodman inspect nginx | grep -i selinux
Troubleshooting
Common Issues and Solutions
-
Container Fails to Start
Terminal window # Check Quadlet syntax/usr/libexec/podman/quadlet -dryrun# View generated unitssystemctl --user cat nginx.service# Check podman directlypodman run --rm docker.io/nginx:alpine -
Network Connectivity Issues
Terminal window # List networkspodman network ls# Inspect networkpodman network inspect app-network# Test connectivitypodman run --rm --network app-network alpine ping gateway -
Volume Permission Problems
Terminal window # Check volume ownershippodman volume inspect app-data# Fix permissions with :Z flagVolume=/data:/data:Z
Debug Mode
# Enable debug loggingSYSTEMD_LOG_LEVEL=debug systemctl --user daemon-reload
# View Quadlet generator logsjournalctl --user -u systemd-generator
# Podman debug logspodman --log-level=debug ps
Best Practices
Production Deployment Workflow
graph LR A[Development] --> B[Testing] B --> C[Staging] C --> D[Production]
E[Local Quadlet] --> A F[CI/CD Pipeline] --> B G[Config Management] --> C H[GitOps] --> D
style A fill:#4ecdc4,stroke:#087f5b,stroke-width:2px style D fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px style H fill:#74c0fc,stroke:#1971c2,stroke-width:2px
Configuration Management
# Directory structure/etc/containers/systemd/├── base/│ ├── common.container│ └── network.network├── apps/│ ├── web.container│ └── api.container└── services/ ├── database.container └── cache.container
Template Example
# base/common.container (template)[Container]User=1000:1000NoNewPrivileges=trueReadOnlyTmpfs=trueEnvironment=TZ=UTCEnvironment=NODE_ENV=production
[Service]Restart=on-failureRestartSec=30TimeoutStartSec=900
Integration Examples
GitLab Runner with Quadlet
[Unit]Description=GitLab RunnerAfter=network-online.target
[Container]Image=docker.io/gitlab/gitlab-runner:latestContainerName=gitlab-runnerVolume=gitlab-runner-config:/etc/gitlab-runner:ZVolume=/var/run/docker.sock:/var/run/docker.sock:ro
[Service]Restart=always
[Install]WantedBy=default.target
Monitoring Stack
# prometheus.container[Unit]Description=Prometheus MonitoringAfter=network-online.target
[Container]Image=docker.io/prom/prometheus:latestContainerName=prometheusPublishPort=9090:9090Volume=prometheus-data:/prometheus:ZVolume=/etc/prometheus:/etc/prometheus:roNetwork=monitoring.network
[Service]Restart=always
[Install]WantedBy=default.target
Migration from Docker Compose
Conversion Script
#!/bin/bashCOMPOSE_FILE=$1OUTPUT_DIR=$2
# Parse compose file and generate Quadlet filespodman-compose -f "$COMPOSE_FILE" config | \while read -r service; do cat > "$OUTPUT_DIR/$service.container" << EOF[Unit]Description=$service ServiceAfter=network-online.target
[Container]# Generated from docker-compose# Manual adjustment required
[Service]Restart=always
[Install]WantedBy=default.targetEOFdone
Conclusion
Podman Quadlet represents a paradigm shift in container management, bringing the power of systemd to containerized applications. Key advantages include:
- Simplified Management: Declarative configuration without complex systemd units
- Native Integration: Containers as first-class systemd services
- Automatic Updates: Built-in container image update support
- Resource Control: Leverage systemd’s advanced resource management
- Production Ready: Enterprise-grade reliability and monitoring
By adopting Quadlet, you gain:
- Unified service management through systemd
- Automatic dependency resolution
- Built-in health checking and restart policies
- Seamless integration with system logging
- Simplified container lifecycle management
Start with simple containers and gradually adopt advanced features as your infrastructure grows. Quadlet makes container management as simple as managing traditional system services.