Podman Quadlet vs Docker Compose for Linux Services

Choose the right container workflow.

Page content

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.

docker compose configuration

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 and keep it out of Git.

Quadlet

Quadlet can use:

[Container]
Environment=APP_ENV=production
EnvironmentFile=/opt/app/app.env

Restrict permissions:

chmod 600 /opt/app/app.env

Neither Compose nor Quadlet is a complete secret-management system by itself. Do not confuse “not in the command line” with “secure”.

Networking

Compose Networking

Compose creates a default project network and gives services DNS names based on service names:

services:
  app:
    image: example/app
    depends_on:
      - db

  db:
    image: postgres:16

The app can usually reach the database at db. Multi-container app networking feels natural. This is one of Compose’s strongest features.

Quadlet Networking

Quadlet can define networks separately with .network files or use Podman network options:

[Network]
NetworkName=appnet

Container file:

[Container]
Image=example/app:stable
Network=appnet.network

This is more explicit and systemd-like. For one or two containers, it is fine. For a large app stack, Compose is often easier to read.

Pods

Podman has a native pod concept. That matters if you like the Kubernetes mental model where multiple containers share a network namespace and lifecycle boundary. Quadlet supports .pod files:

[Pod]
PodName=myapp
PublishPort=8080:8080

A container can join that pod:

[Container]
Image=ghcr.io/example/app:stable
Pod=myapp.pod

Compose does not have the same pod model. It has services on networks. For most simple web apps, Compose networks are enough. For Podman users who like pod-style grouping, Quadlet is a better match.

Build Workflows

Compose is usually better when you build images as part of the local application workflow:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"

Then:

docker compose up --build

This is extremely convenient for development. Quadlet is usually better when you run already-built images as services. Build images with Podman separately:

podman build -t localhost/myapp:latest .

Then reference the image:

[Container]
Image=localhost/myapp:latest

If your workflow is “edit code, rebuild, restart stack”, Compose wins. If your workflow is “deploy a known image as a Linux service”, Quadlet wins.

Portability

Compose files are widely shared. Many open-source projects provide a compose.yaml or docker-compose.yml. If a project says “run this with Docker Compose”, you can usually start quickly with docker compose up -d. This is a major practical advantage.

Quadlet is portable across systems that have Podman, systemd, and compatible Quadlet support. That is a narrower target, but a very good one for modern Linux servers. Quadlet is not the best format for sharing an application with every possible developer. It is a good format for describing how a specific Linux host should run a containerized service.

Developer Experience

Docker Compose usually wins developer experience. More examples, more tutorials, more project templates, easier local builds, easy one-file stack, familiar docker compose up, and strong fit for dev dependencies. A developer can read this quickly:

services:
  db:
    image: postgres:16
  redis:
    image: redis:7
  app:
    build: .

Quadlet can do similar things, but it is more operations-shaped. For local development, I would rarely start with Quadlet unless the application itself is specifically about Podman or systemd.

Operations Experience

Quadlet often wins operations experience on a Linux host. Reasons:

  • Native systemctl
  • Native journalctl
  • Rootless user services
  • systemd dependencies
  • systemd timers
  • systemd restart behavior
  • No central Docker daemon
  • Better fit with host service management

A server admin can reason about:

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

That is the normal Linux service workflow.

Security Model

Docker Compose Security Notes

Docker Compose usually talks to the Docker daemon. On a normal Linux Docker install, access to the Docker socket is powerful. A user who can control Docker can often mount host paths, run privileged containers, or otherwise gain broad host control. For installation options including rootless Docker on Ubuntu, see Install Docker on Ubuntu.

Practical advice:

  • Do not casually expose /var/run/docker.sock.
  • Treat the docker group as privileged.
  • Avoid privileged containers.
  • Avoid host mounts unless needed.
  • Keep secrets out of Git.
  • Use explicit image tags for important services.
  • Review published ports.

Quadlet Security Notes

Podman Quadlet pairs well with rootless containers and user systemd services. This can reduce risk, especially on shared hosts or personal servers where services should not require a root daemon.

Practical advice:

  • Prefer rootless services when they fit.
  • Use user units for user-owned services.
  • Use system units only when host-level privileges are needed.
  • Avoid unnecessary privileged containers.
  • Keep environment files locked down.
  • Think carefully about bind mounts.

Rootless does not mean risk-free. It means the default blast radius can be smaller.

Performance

For most web services, internal tools, and self-hosted apps, performance is not the deciding factor. The main differences are operational, not raw speed. Choose based on lifecycle model, security model, host integration, team familiarity, update process, networking needs, and debugging workflow.

If you are choosing between Compose and Quadlet because of performance alone, you are probably optimizing the wrong layer.

Failure Modes

Docker Compose Failure Modes

Common problems:

  • Docker daemon not running
  • Compose plugin missing
  • Wrong project directory
  • .env not loaded as expected
  • Old docker-compose binary used by accident
  • Containers not recreated after config changes
  • Orphan containers left after service rename
  • Volumes deleted with down -v
  • Docker logs filling the disk
  • Docker socket permission errors

Best fixes:

docker compose config
docker compose ps
docker compose logs -f
docker compose up -d --remove-orphans

Podman Quadlet Failure Modes

Common problems:

  • Unit file in the wrong directory
  • Forgot systemctl daemon-reload
  • Using system units when user units were intended
  • Forgot loginctl enable-linger for rootless services
  • Image pull takes longer than systemd startup timeout
  • cgroup v2 not available
  • SELinux labels or volume permissions
  • Service name differs from file expectations
  • Network or volume unit not enabled or referenced correctly

Best fixes:

systemctl status app.service
journalctl -u app.service -f
systemctl daemon-reload
podman ps -a
podman logs container-name

For rootless:

systemctl --user status app.service
journalctl --user -u app.service -f

Migration Example: Compose to Quadlet

Start with this Compose service:

services:
  whoami:
    image: traefik/whoami:v1.10
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      WHOAMI_NAME: compose-demo

Run with Compose:

docker compose up -d

A rough Quadlet equivalent:

[Unit]
Description=Whoami demo container
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/traefik/whoami:v1.10
PublishPort=8080:80
Environment=WHOAMI_NAME=quadlet-demo

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

Save as /etc/containers/systemd/whoami.container, then:

sudo systemctl daemon-reload
sudo systemctl enable --now whoami.service
sudo systemctl status whoami.service

The example is easy because it is one container. A larger Compose stack with databases, networks, volumes, and build steps needs more careful translation.

Migration Checklist

Before moving from Compose to Quadlet, ask:

[ ] Is this stack really a set of long-running host services?
[ ] Are the images already built and published?
[ ] Do I need rootless services?
[ ] Do I want systemd dependencies and timers?
[ ] Are volumes and bind mounts clearly understood?
[ ] Are ports documented?
[ ] Are secrets handled outside Git?
[ ] Is there a backup and restore process?
[ ] Can I monitor logs through journalctl?
[ ] Do I have a rollback path?

If most answers are yes, Quadlet may be a good fit. If the stack is mostly for local development, Compose is probably still better.

When to Stay with Docker Compose

Stay with Compose when:

  • The project already ships a good Compose file.
  • You need the easiest onboarding path.
  • Developers run the same stack locally.
  • You build images during development.
  • You want one YAML file for services, volumes, and networks.
  • You want maximum tutorial and community compatibility.
  • Your current systemd wrapper works fine.

There is no prize for migrating a working Compose stack to Quadlet just because Quadlet is cleaner in theory. If Compose is boring and reliable for your use case, keep it.

When to Move to Podman Quadlet

Move to Quadlet when:

  • The stack is really a host service.
  • You want rootless service management.
  • You prefer Podman over Docker.
  • You want systemd to own lifecycle.
  • You want journalctl logs.
  • You want service dependencies.
  • You want user services that survive logout.
  • You want less Docker daemon exposure.
  • You are building a self-hosting host around systemd.

Quadlet is not “Compose but better.” It is a different design center.

Pattern 1: Local Development

Use Docker Compose. Fast, familiar, portable, easy to rebuild, easy for teams.

Pattern 2: Single-Host Self-Hosting

Use either. Choose Compose if the project already provides a Compose file. Choose Quadlet if you want systemd-native service management. Compose gives a better app bundle; Quadlet gives a better Linux service.

Pattern 3: User-Owned Rootless Service

Use Podman Quadlet. Rootless workflow, user-level service management, no central Docker daemon.

~/.config/containers/systemd/app.container
systemctl --user enable --now app.service
loginctl enable-linger

Pattern 4: Production-Like Single Server

Use Docker Compose with a disciplined systemd wrapper, or use Quadlet if your team is comfortable with Podman. Do not choose based on fashion. Choose based on who will operate it at 2 AM.

Pattern 5: Multi-Node Platform

Use neither as the final orchestration layer. Consider Kubernetes, Nomad, Swarm, or a managed platform. Compose and Quadlet are excellent single-host tools. They are not cluster schedulers.

Practical Decision Tree

Is this mainly for local development?
  yes:
    use Docker Compose
  no:
    continue

Does the project already provide a maintained compose.yaml?
  yes:
    use Docker Compose unless you have a strong reason to migrate
  no:
    continue

Do you want rootless long-running services managed by systemd?
  yes:
    use Podman Quadlet
  no:
    continue

Do you want the easiest multi-container app definition?
  yes:
    use Docker Compose
  no:
    continue

Do you want containers to behave like normal Linux services?
  yes:
    use Podman Quadlet
  no:
    use Docker Compose

Side-by-Side Commands

Task Docker Compose Podman Quadlet
Start docker compose up -d systemctl start app.service
Stop docker compose down systemctl stop app.service
Restart docker compose restart systemctl restart app.service
Apply changes docker compose up -d systemctl daemon-reload && systemctl restart app.service
Logs docker compose logs -f journalctl -u app.service -f
Status docker compose ps systemctl status app.service
Enable on boot systemd wrapper or restart policy systemctl enable app.service
Pull update docker compose pull podman pull image
Rootless service possible natural with systemctl --user

Common Misunderstandings

“Quadlet Replaces Docker Compose”

Not exactly. Quadlet replaces some Compose use cases, especially long-running Linux services. It does not replace Compose as the easiest application-stack format for developers.

“Docker Compose Is Not Production Ready”

Too broad. Compose can be perfectly reasonable for small production systems if you understand backups, updates, logging, restart behavior, and host security. The problem is not Compose. The problem is pretending a single-host Compose deployment has the same properties as a cluster orchestrator.

“Podman Is Just Docker Without the Daemon”

Too simple. Podman has Docker-compatible commands, but its design center is different: daemonless operation, rootless workflows, pods, and Linux integration.

“Rootless Means Secure”

No. Rootless reduces some risks. It does not make bad images, exposed secrets, unsafe bind mounts, or vulnerable apps safe.

“systemd Is Too Heavy for Containers”

systemd is already the service manager on most mainstream Linux servers. Using it to manage long-running containers is not strange. It is often the boring and correct thing to do.

Final Recommendation

Use Docker Compose when the application stack is the main thing. Use Podman Quadlet when the Linux service is the main thing.

That distinction is more useful than arguing which tool is better. For developer workflows, Compose is hard to beat. It is popular, readable, portable, and supported by countless projects. For long-running Linux services, Quadlet is quietly excellent. It makes containers feel like native systemd services, works naturally with rootless Podman, and fits the operational model of a serious Linux host.

The preferred split:

Local development: Docker Compose
Portable app examples: Docker Compose
Small self-hosted stacks: Docker Compose or Quadlet
Rootless user services: Podman Quadlet
Long-running host services: Podman Quadlet
Multi-node orchestration: neither; use a real orchestrator

Do not migrate just to be modern. Migrate when the lifecycle model is better. Compose is a great stack tool. Quadlet is a great service tool. The smart choice is to use each where its mental model matches the job.

References

Subscribe

Get new posts on AI systems, Infrastructure, and AI engineering.