·10 min read

Podman Quadlets: Declarative Container Management

Learn Podman Quadlets for declarative container management. Define containers as systemd units with auto-restart, dependencies, and resource limits.

PodmanContainersSystemdDevOpsLinux

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 name
  • After — Start after these units
  • Wants — 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 container
  • Port — Port mapping (host:container)
  • Volume — Volume mount (source:dest:options)
  • Environment — Environment variables
  • Network — Network mode or name
  • Pod — Add container to pod
  • AutoUpdate — 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 restart
  • TimeoutStartSec — Startup timeout
  • ExecStartPre — Run before container starts
  • ExecStartPost — 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

  1. Use descriptive names — nginx.container not web.container
  2. Set restart policies — Restart=always for critical services
  3. Define dependencies — Use After= and Requires= correctly
  4. Limit resources — Set CPU and memory limits
  5. Use volumes — Persist data outside containers
  6. Monitor logs — Check journald regularly
  7. Test locally — Validate quadlets before production
  8. 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.