DEV Community

Podman Quadlet vs Docker Compose for Linux Services

Podman Quadlet vs Docker Compose for Linux Services

Docker Compose and Podman Quadlet solve overlapping problems but come from different design centers, and choosing between them depends on whether you think in application stacks or Linux services. The distinction matters for anyone running containers on a Linux host beyond a single afternoon of experimentation.

Compose describes services, networks, and volumes in a YAML file and starts them with docker compose up. Quadlet describes containers in systemd-style unit files and lets the system service manager own the lifecycle. Both approaches work for self-hosted services, internal tools, and small servers.

The right choice comes down to your operational model, team familiarity, and whether you prefer a developer-friendly stack format or a systemd-native service model. This comparison covers the practical differences: file formats, lifecycle ownership, rootless containers, logging, updates, networking, security, and migration paths. It is part of Developer Tools: The Complete Guide to Modern Development Workflows.

Quick Recommendation

Use Docker Compose when:

  • You want the fastest multi-container workflow
  • You already use Docker Engine
  • You share stacks with developers
  • You need a familiar compose.yaml for local development
  • You deploy small services with Docker on one host
  • You use existing Compose examples from projects

Use Podman Quadlet when:

  • You want systemd-native container services
  • You prefer rootless containers
  • You do not want a central Docker daemon
  • You run long-lived services on a Linux host
  • You want systemctl, journalctl, timers, dependencies, and auto-start
  • You are building a self-hosted or homelab server around systemd
  • You want containers to fit into the normal Linux service model

The practical rule: Docker Compose is better for application stacks. Podman Quadlet is better for Linux services. That is not a law. It is a useful default.

Comparison Table

Area Docker Compose Podman Quadlet
Primary model Multi-container application systemd-managed container service
File format YAML systemd-like unit files
Runtime Docker Engine Podman
Daemon Uses Docker daemon Daemonless Podman model
Service manager Compose manages stack lifecycle systemd manages lifecycle
Best fit Dev stacks, app bundles, simple deployments Long-running Linux services
Rootless support Possible, but not the default mental model Strong fit
Logs docker compose logs journalctl and podman logs
Startup on boot Usually via systemd wrapper or restart policy Native systemd unit
Updates docker compose pull && docker compose up -d Podman auto-update or systemd workflows
Portability Very high across Docker environments Best on Linux with systemd
Learning curve Easier for most developers Easier for systemd users
Ecosystem examples Huge Smaller, but growing

Neither one is Kubernetes. Most small services do not need a cluster. They need a boring, understandable way to start, stop, update, log, and recover.

What Docker Compose Is Good At

Docker Compose is a tool for defining and running multi-container applications. A typical Compose file describes services, images, build contexts, ports, volumes, networks, environment variables, health checks, dependencies, and profiles.

services:
  web:
    image: nginx:stable
    ports:
      - "8080:80"
    restart: unless-stopped
    volumes:
      - ./html:/usr/share/nginx/html:ro
  redis:
    image: redis:7
    restart: unless-stopped

Run it:

docker compose up -d

Check status:

docker compose ps

Read logs:

docker compose logs -f

Stop it:

docker compose down

Compose is direct and productive. It is especially good when the unit of thought is "this application has several containers." For a comprehensive reference of Compose commands and patterns, see the Docker Compose Cheatsheet. For Docker commands beyond Compose - images, volumes, networks, and cleanup - see the Docker Cheatsheet.

What Podman Quadlet Is Good At

Podman Quadlet is a way to define Podman containers using systemd-style files. Instead of writing a full generated systemd service by hand, you write a declarative file:

[Unit]
Description=Example web container
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/library/nginx:stable
PublishPort=8080:80
Volume=/opt/example/html:/usr/share/nginx/html:ro

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

Save it as /etc/containers/systemd/example.container, then reload systemd:

sudo systemctl daemon-reload

Start it:

sudo systemctl enable --now example.service

Check it:

systemctl status example.service
journalctl -u example.service -f

The core appeal of Quadlet: the container becomes a normal Linux service.

The Philosophical Difference

Docker Compose Thinks in Stacks

Compose asks: What services make up this application? A Compose project usually lives near application code:

myapp/
  compose.yaml
  .env
  app/
  db/

You start the project as a unit:

docker compose up -d

You update the project as a unit:

docker compose pull
docker compose up -d

This is simple, visible, and portable.

Podman Quadlet Thinks in Services

Quadlet asks: What containers should this Linux host run as services? Quadlet files live in systemd-related container paths:

  • /etc/containers/systemd/
  • ~/.config/containers/systemd/

You manage generated services with systemd:

systemctl status myapp.service
systemctl restart myapp.service
journalctl -u myapp.service -f

This feels more native on a Linux server. For general systemd service patterns, see Run any Executable as a Service in Linux.

The Important Difference: Who Owns Lifecycle?

With Docker Compose, Compose owns the application lifecycle. With Quadlet, systemd owns the service lifecycle. This affects boot behavior, shutdown behavior, restart policy, dependency ordering, logs, health visibility, user services, updates, integration with timers, and integration with other host services.

If you already use systemd to manage everything else on the host, Quadlet fits neatly. If you think mainly in terms of application stacks, Compose is usually more comfortable.

Docker Compose Under systemd vs Quadlet

You can run Docker Compose as a systemd service. That is often a good pattern. Example systemd unit:

[Unit]
Description=MyApp Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecReload=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

This works well. But it is still a wrapper around Compose. systemd starts the Compose command, while Docker and Compose handle containers behind it.

With Quadlet, the unit generation is designed for Podman and systemd directly. You write container-oriented unit files, and systemd manages the generated services. The distinction is subtle but important:

  • Docker Compose under systemd: systemd manages a Compose command.
  • Podman Quadlet: systemd manages generated container services.

For a detailed walkthrough of this pattern, see Run Docker Compose as a Linux Service with systemd.

File Format Comparison

Docker Compose YAML

Compose uses YAML. It is compact, popular, and easy to share. It is also indentation-sensitive and can grow messy when a stack becomes large.

services:
  app:
    image: ghcr.io/example/app:1.0.0
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      APP_ENV: production
    volumes:
      - app-data:/data

volumes:
  app-data:

Quadlet Unit Files

Quadlet uses systemd-like files. They are more verbose when you have many services, but readable if you already understand systemd.

[Unit]
Description=Example app container
After=network-online.target
Wants=network-online.target

[Container]
Image=ghcr.io/example/app:1.0.0
PublishPort=8080:8080
Environment=APP_ENV=production
Volume=app-data.volume:/data

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

And a volume file:

[Volume]
VolumeName=app-data

Saved as app.container and app-data.volume in the appropriate systemd container directory.

Mapping Docker Compose Concepts to Quadlet

Compose concept Quadlet equivalent
services .container files or .pod plus .container
volumes .volume files or bind mounts
networks .network files
ports PublishPort=
environment Environment= or EnvironmentFile=
restart [Service] Restart=
depends_on systemd After=, Wants=, Requires=
healthcheck Podman healthcheck options
profiles systemd enablement and separate units
docker compose logs journalctl -u service and podman logs
docker compose up -d systemctl start service
docker compose down systemctl stop service
project directory systemd container unit directory

The migration is conceptually simple but not mechanical. Compose describes a stack. Quadlet describes services.

Rootless Containers

Rootless containers are one of the strongest reasons to look at Podman and Quadlet. With Docker, many users add themselves to the docker group. That is convenient, but access to the Docker daemon is effectively powerful host access. On a personal workstation, that may be acceptable. On shared servers, it deserves more caution.

Podman was designed with rootless usage as a first-class workflow. A rootless Quadlet lives under the user's config directory:

~/.config/containers/systemd/whoami.container

Then manage it with user systemd:

systemctl --user daemon-reload
systemctl --user enable --now whoami.service
systemctl --user status whoami.service
journalctl --user -u whoami.service -f

To allow the user service to keep running after logout:

sudo loginctl enable-linger "$USER"

This is a clean model for user-owned services.

Rootless Comparison

Area Docker Compose Podman Quadlet
Default common setup Rootful Docker daemon Rootless-friendly Podman
User service model Possible, but less native Native with systemctl --user
Daemon access risk Docker socket is powerful No central root daemon by default
Low port binding Simple as rootful Docker Needs extra setup when rootless
Host integration Very common More Linux-native
Shared server fit Needs care Strong fit

Rootless is not magic. It has tradeoffs around networking, privileged behavior, and low ports. But for long-running user-owned services, Quadlet is a very elegant model.

Startup and Boot Behavior

Docker Compose

Compose by itself does not create a boot service. You usually rely on Docker restart policies, a systemd wrapper around docker compose up -d, a deployment script, or a higher-level tool.

Example Compose restart policy:

services:
  app:
    image: example/app:stable
    restart: unless-stopped

A systemd wrapper gives you a host-level service. That is good, but it is still an extra wrapper. See Run Docker Compose as a Linux Service with systemd for the full pattern.

Podman Quadlet

Quadlet is already systemd-oriented. Enable on boot:

sudo systemctl enable myapp.service

For rootless:

systemctl --user enable myapp.service
sudo loginctl enable-linger "$USER"

Boot behavior is not an add-on. It is the model.

Restart Behavior

Compose commonly uses restart: unless-stopped in YAML. Quadlet commonly uses systemd restart behavior:

[Service]
Restart=always

Or:

[Service]
Restart=on-failure

This moves restart logic into the service manager. The preference: use Compose/Docker restart policies for Compose stacks, use systemd restart policies for Quadlet, and do not stack too many supervisors. Keep one clear owner of restart behavior.

Logging Comparison

Docker Compose Logs

docker compose logs -f
docker compose logs -f app

This is excellent for developers.

Quadlet Logs

Quadlet services use systemd logs:

journalctl -u app.service -f

For rootless units:

journalctl --user -u app.service -f

You can still use Podman logs:

podman logs -f container-name

For server operations, journalctl integration is a major advantage. Your containers fit into the same log workflow as other Linux services.

Updates

Updating Docker Compose

A common update flow:

cd /opt/myapp
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f

Easy to wrap in a script:

#!/usr/bin/env bash
set -euo pipefail
cd /opt/myapp
docker compose config --quiet
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
docker compose ps

Updating Podman Quadlet

A simplified manual flow:

sudo podman pull ghcr.io/example/app:1.0.1
sudo systemctl restart app.service

Or for rootless:

podman pull ghcr.io/example/app:1.0.1
systemctl --user restart app.service

Podman can support auto-update workflows when containers are configured with the right labels and image policy. Quadlet's advantage is not that updates are always simpler. The advantage is that updates are service-manager-native.

Auto-Update Philosophy

Auto-updates are convenient. They are also a risk. For low-risk homelab services, automatic container updates can be fine. For databases, stateful apps, or business services, the preferred flow is:

  1. Back up.
  2. Pull.
  3. Recreate or restart.
  4. Check health.
  5. Prune later.

Compose makes this explicit. Quadlet and Podman can make it systemd-native. Neither tool removes the need for a rollback plan.

Volumes and Persistent Data

Compose Volumes

Compose supports named volumes:

services:
  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

And bind mounts:

services:
  app:
    image: example/app
    volumes:
      - ./config:/config:ro
      - ./data:/data

Quadlet Volumes

Quadlet can use bind mounts:

[Container]
Volume=/opt/app/config:/config:ro
Volume=/opt/app/data:/data

Or a .volume file:

[Volume]
VolumeName=app-data

Then reference it:

[Container]
Volume=app-data.volume:/data

Compose is more compact for stack-level storage. Quadlet is more aligned with independently managed service units.

Secrets and Environment Files

Compose

Compose often uses env_file or environment in YAML. For a small private service, .env is common. For serious systems, treat .env as sensitive.

Comments

No comments yet. Start the discussion.