Podman Quadlets: Declarative Container Management
Learn Podman Quadlets for declarative container management. Define containers as systemd units with auto-restart, dependencies, and resource limits.
Podman Quadlets let you define containers as systemd unit files. This declarative approach brings auto-restart, dependency management, and resource controls to rootless containers.
What Are Quadlets?
Quadlets are systemd unit files that define Podman containers. Instead of running podman run manually, you create a .container file and let systemd manage the lifecycle.
Benefits:
- ✅ Automatic startup on boot
- ✅ Auto-restart on failure
- ✅ Dependency management
- ✅ Resource limits (CPU, memory)
- ✅ Logging via journald
- ✅ Integration with systemctl
Basic Quadlet Example
Step 1: Create Container File
# ~/.config/containers/systemd/nginx.container
[Unit]
Description=Nginx Web Server
After=network-online.target
[Container]
Image=docker.io/library/nginx:latest
Port=8080:80
Volume=./html:/usr/share/nginx/html:ro
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target
Step 2: Generate Systemd Unit
# Reload systemd to detect new quadlet
systemctl --user daemon-reload
# Check generated unit
systemctl --user cat nginx.service
Step 3: Start Container
# Start container
systemctl --user start nginx.service
# Check status
systemctl --user status nginx.service
# Enable auto-start on boot
systemctl --user enable nginx.service
Quadlet File Structure
A quadlet file has four sections:
[Unit] Section
Standard systemd unit configuration:
[Unit]
Description=My Container
Documentation=https://example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
Common directives:
Description— Human-readable nameAfter— Start after these unitsWants— Weak dependency (start if available)Requires— Strong dependency (must be running)
[Container] Section
Podman-specific container configuration:
[Container]
Image=docker.io/library/nginx:latest
ContainerName=my-nginx
Port=8080:80
Volume=./data:/data:Z
Environment=NODE_ENV=production
EnvironmentFile=./env.list
Network=host
Pod=my-pod
AutoUpdate=registry
Common directives:
Image— Container image (required)ContainerName— Name for the containerPort— Port mapping (host:container)Volume— Volume mount (source:dest:options)Environment— Environment variablesNetwork— Network mode or namePod— Add container to podAutoUpdate— Auto-update image from registry
[Service] Section
Standard systemd service configuration:
[Service]
Restart=always
RestartSec=10
TimeoutStartSec=900
ExecStartPre=/usr/bin/mkdir -p ./data
ExecStartPost=/usr/bin/curl -X POST http://localhost:8080/health
Common directives:
Restart— When to restart (always, on-failure, no)RestartSec— Delay before restartTimeoutStartSec— Startup timeoutExecStartPre— Run before container startsExecStartPost— Run after container starts
[Install] Section
Standard systemd install configuration:
[Install]
WantedBy=default.target
Advanced Examples
Multi-Service Stack
# postgres.container
[Unit]
Description=PostgreSQL Database
After=network-online.target
[Container]
Image=docker.io/library/postgres:16
ContainerName=postgres
Volume=pgdata:/var/lib/postgresql/data
Environment=POSTGRES_PASSWORD_FILE=/run/secrets/db_password
PublishPort=5432:5432
[Service]
Restart=always
[Install]
WantedBy=default.target
# api.container
[Unit]
Description=API Server
After=postgres.service
Requires=postgres.service
[Container]
Image=myapp/api:latest
ContainerName=api
Environment=DATABASE_URL=postgresql://postgres:5432/myapp
PublishPort=3000:3000
[Service]
Restart=always
RestartSec=10
[Install]
WantedBy=default.target
Resource Limits
# resource-limited.container
[Unit]
Description=Resource Limited Container
[Container]
Image=myapp/worker:latest
ContainerName=worker
[Service]
# CPU limit (100000 = 1 CPU)
CPUQuota=200000
# Memory limit
MemoryMax=512M
MemoryHigh=400M
# Restart policy
Restart=on-failure
RestartSec=30
[Install]
WantedBy=default.target
Pod with Multiple Containers
# mypod.pod
[Pod]
PodName=mypod
PublishPort=8080:80
PublishPort=8443:443
# web.container
[Unit]
Description=Web Server
After=mypod-pod.service
[Container]
Image=docker.io/library/nginx:latest
Pod=mypod.pod
Volume=./html:/usr/share/nginx/html:ro
[Service]
Restart=always
[Install]
WantedBy=default.target
# api.container
[Unit]
Description=API Server
After=mypod-pod.service
[Container]
Image=myapp/api:latest
Pod=mypod.pod
Environment=NODE_ENV=production
[Service]
Restart=always
[Install]
WantedBy=default.target
Managing Quadlets
View Status
# List all quadlet services
systemctl --user list-units '*.service' | grep container
# Check specific service
systemctl --user status nginx.service
# View logs
journalctl --user -u nginx.service -f
Control Services
# Start/stop/restart
systemctl --user start nginx.service
systemctl --user stop nginx.service
systemctl --user restart nginx.service
# Enable/disable auto-start
systemctl --user enable nginx.service
systemctl --user disable nginx.service
Update Images
# Pull new image
podman pull docker.io/library/nginx:latest
# Restart service to use new image
systemctl --user restart nginx.service
# Or use auto-update
podman auto-update
Quadlet vs Docker Compose
| Feature | Quadlets | Docker Compose |
|---|---|---|
| Auto-start on boot | ✅ Native | ❌ Manual |
| Auto-restart | ✅ Native | ✅ restart: always |
| Resource limits | ✅ systemd | ✅ deploy.resources |
| Dependency management | ✅ Native | ✅ depends_on |
| Logging | ✅ journald | ✅ docker logs |
| Rootless support | ✅ Native | ⚠️ Requires config |
| Pod support | ✅ Native | ❌ No |
| Multi-host | ❌ No | ✅ Swarm/K8s |
Migration from Docker Compose
Docker Compose File
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: always
Equivalent Quadlet
# web.container
[Unit]
Description=Nginx Web Server
After=network-online.target
[Container]
Image=docker.io/library/nginx:latest
Port=8080:80
Volume=./html:/usr/share/nginx/html:ro
[Service]
Restart=always
[Install]
WantedBy=default.target
FoxTerm Quadlet Support
FoxTerm makes Quadlet management visual and intuitive:
Visual Quadlet Editor
- Create quadlet files with syntax highlighting
- Validate configuration before deployment
- Preview generated systemd units
- One-click deploy
Service Dashboard
- View all quadlet services
- Real-time status and logs
- Resource usage monitoring
- One-click start/stop/restart
Template Library
- Pre-built quadlet templates for common services
- PostgreSQL, Redis, Nginx, Node.js, Python
- Customize and deploy in seconds
Integration
- Import Docker Compose files
- Auto-convert to quadlets
- Manage pods and networks
- Monitor all services in one place
Best Practices
- Use descriptive names —
nginx.containernotweb.container - Set restart policies —
Restart=alwaysfor critical services - Define dependencies — Use
After=andRequires=correctly - Limit resources — Set CPU and memory limits
- Use volumes — Persist data outside containers
- Monitor logs — Check journald regularly
- Test locally — Validate quadlets before production
- Version control — Store quadlet files in git
Conclusion
Podman Quadlets bring the power of systemd to container management. Declarative configuration, automatic lifecycle management, and deep OS integration make Quadlets ideal for production workloads.
FoxTerm makes Quadlets accessible with visual editing, templates, and monitoring.
FoxTerm Pro includes Podman Quadlet management. Download at foxterm.app.