Dev Encyclopedia
ArticlesToolsContactAbout

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Dev.to
Discord
WhatsApp Channel
daily.dev
Hashnode
X

Content

  • Articles
  • Tools
  • About
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

© 2026 Dev Encyclopedia

Back to top ↑
  1. Home
  2. /
  3. Tools
  4. /
  5. QuadletGen
Free · Private · No setup

Convert docker-compose.yml to Podman Quadlet files in seconds.

Paste your docker-compose.yml. Get ready-to-run .container, .network, and .volume files with a full conversion coverage report. Nothing leaves your browser.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

How QuadletGen works

  1. 1

    Paste your compose file or load the example

    Paste the full docker-compose.yml, or click "Load example" to see a working two-service stack converted immediately.

  2. 2

    The file is parsed in your browser

    js-yaml parses the YAML into services, volumes, and networks entirely client-side. No file ever leaves your browser.

  3. 3

    Choose rootless or rootful

    The toggle changes the destination file path and the systemd WantedBy= target in the generated [Install] section to match how you'll deploy.

  4. 4

    Each service maps to a .container file

    image, container_name, ports, environment, volumes, networks, depends_on, restart, command, and healthcheck are walked and converted to their Quadlet key equivalents.

  5. 5

    Named volumes and networks get their own files

    Any named volume or network referenced by a service automatically generates a matching .volume or .network unit file, shown as its own tab.

  6. 6

    Review the coverage report, then copy or download

    Every converted, skipped, or warning-flagged key is listed with a plain-English reason. Copy files individually or download the full set as a zip, then follow the next steps panel.

What the coverage report means

Every compose key that QuadletGen touches gets one of three statuses, so you know exactly what to double-check before deploying.

Converted

The compose key mapped cleanly to a Quadlet key with no ambiguity. Ports, environment variables, bind mounts, and restart policies usually fall here.

ports:
  - "8080:80"
# -> PublishPort=8080:80 (Converted)
Warning

The key converted, but with a caveat worth reading. Common example: depends_on with condition: service_healthy converts to After=/Requires= but systemd doesn't wait for the healthcheck to pass the way Compose does.

depends_on:
  db:
    condition: service_healthy
# -> After=db.service, Requires=db.service (Warning: no health-gated wait)
Skipped

The key is outside Quadlet's scope or not part of the MVP conversion set: build sections and secrets are the two most common. The coverage report tells you the manual equivalent.

build:
  context: .
# -> Skipped: build an image separately, then set image:

docker-compose.yml to Quadlet key reference

Every compose key QuadletGen understands, and the Quadlet key it maps to.

Compose keyQuadlet keyNote
imageImage=Direct mapping
container_nameContainerName=Direct mapping, defaults to service name
portsPublishPort=One line per port mapping
environmentEnvironment=One line per KEY=VALUE, both map and array syntax
volumes (bind mount)Volume=/host/path:/container/pathDirect mapping
volumes (named)Volume=name.volume:/container/pathGenerates a separate name.volume file
networksNetwork=name.networkGenerates a separate name.network file
depends_onAfter= / Requires=Ordering only; systemd doesn't wait for service_healthy
restartRestart=unless-stopped maps to always (closest systemd equivalent)
commandExec=Array or string form both supported
healthcheck.testHealthCmd=CMD and CMD-SHELL forms supported
healthcheck.intervalHealthInterval=Plus HealthTimeout=, HealthRetries=, HealthStartPeriod=
build(not converted)Build the image separately, then set image:
secrets(not converted)Use podman secret create, then Secret= manually

When to use QuadletGen

ScenarioWhat to paste
Migrating a homelab stack (Jellyfin, Nextcloud, Immich) to PodmanYour existing docker-compose.yml
Deploying a project's README compose file on a Podman/RHEL hostThe compose file from the project README
Learning Quadlet INI syntax from a file you already knowA small, familiar 1-2 service compose file
Checking whether a stack has unsupported build or secrets sectionsFull compose file, then review the Skipped items
Deciding between rootless and rootful before deployingAny compose file, toggle both modes to compare paths
Standardizing a fleet of RHEL servers on systemd-managed containersEach server's current compose file, one at a time

Frequently Asked Questions

What does QuadletGen do?

QuadletGen converts a docker-compose.yml file into the equivalent Podman Quadlet unit files: one .container file per service, plus .volume files for named volumes and .network files for defined networks. It runs entirely in your browser, no install and no upload required.

Alongside the generated files, it produces a coverage report listing exactly which compose keys converted and which were skipped, so you know what still needs manual attention before you deploy.

How do I convert my docker-compose.yml to Quadlet?

Paste your compose file into the textarea above, or click "Load example" to see a working sample first. Choose Rootless or Rootful depending on where you'll deploy, then review the generated .container, .volume, and .network files in the tabs.

Copy each file individually, or use "Download all (.zip)" to get every generated unit file at once. Then follow the next steps panel to place the files and reload systemd.

bash
# Rootless
mkdir -p ~/.config/containers/systemd/
cp *.container *.volume *.network ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start web.service
How is QuadletGen different from podlet?
podlet (CLI)QuadletGen
Setup requiredRust toolchain or binary installNone, runs in browser
Variable substitutionHard-fails on unresolved variablesConverts what it can, flags the rest
OutputSingle stdout stream to split by handPer-file tabs plus zip download
Coverage reportNonePer-key converted/skipped/warning breakdown

podlet is a solid CLI and more actively maintained for edge cases. QuadletGen is for the common case: pasting a compose file on a phone or a machine without Rust installed, and getting a working starting point with a clear list of what to double-check.

Does QuadletGen upload my compose file anywhere?

No. Parsing, conversion, and zip generation all happen in JavaScript in your browser using js-yaml and JSZip. Nothing is sent to a server. This matters because compose files commonly contain database passwords and other secrets in plain text.

Should I use rootless or rootful Quadlet?

Rootless Quadlet units live in ~/.config/containers/systemd/ and are managed with systemctl --user, running as your regular user account. This is the recommended default for most homelab and self-hosting setups, since a compromised container has no root access.

Rootful units live in /etc/containers/systemd/ and are managed with sudo systemctl, running as root. Use rootful only when a service genuinely needs privileged ports below 1024 without extra configuration, or low-level host access. Toggle between the two above to see how the destination path and WantedBy= target change.

What compose features doesn't QuadletGen convert?

build: sections aren't converted since Quadlet needs a pre-built image reference. Build the image separately (or pull a published one) and set image: to its tag before converting. secrets: entries aren't converted either; the coverage report flags them and you'll need podman secret create plus a Secret= line added manually.

Compose-level profiles, deploy, configs, and extends are outside Quadlet's scope entirely and are not part of this converter's output.

Does depends_on with condition: service_healthy carry over?

The dependency itself converts to After= and Requires= so systemd starts services in the right order. But systemd doesn't have a native concept of waiting for a container's healthcheck to pass before starting the next unit the way Compose's service_healthy condition does.

The coverage report flags this explicitly. If strict health-gated startup ordering matters, you'll need an additional ExecStartPre health-poll script or a retry loop in the dependent service.

Related reading

Guide

Docker Compose to Podman Quadlet: A Practical Migration Guide

The step-by-step manual migration walkthrough, including rootless setup and the daemon-reload gotchas. Use the converter above to skip the hand-translation.

Tool

Cron to systemd Timer Converter

Migrating cron jobs to the same systemd-managed host? Convert crontab lines to .timer and .service unit pairs.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

Full stack developer with over 6 years of experience building production applications. Writes practical guides on JavaScript, TypeScript, React, Node.js, and cloud infrastructure. Focused on helping developers solve real problems with clean, maintainable code.

Enjoyed this article?

Get practical dev guides, tool updates, and new articles delivered straight to your inbox. No spam, unsubscribe anytime.

Generated Quadlet files

[Unit]
Description=api (converted from docker-compose.yml)

[Container]
Image=myorg/api:1.4.0
ContainerName=api
Environment=DATABASE_URL=postgres://user:pass@db:5432/app
Network=frontend.network
Network=backend.network
Exec=node server.js

[Service]
Restart=always

[Install]
WantedBy=default.target

Conversion coverage

web.imageConverted

Mapped to Image=

web.container_nameConverted

Mapped to ContainerName=

web.portsConverted

1 port mapping(s) converted to PublishPort=

web.environmentConverted

1 variable(s) converted to Environment=

web.volumesConverted

2 volume(s) converted. Named volumes generate their own .volume file.

web.networksConverted

1 network(s) converted to Network=

web.depends_onConverted

1 dependency(ies) converted to After=/Requires=.

web.restartConverted

systemd has no exact 'unless-stopped' equivalent; mapped to Restart=always.

web.healthcheckConverted

Mapped to HealthCmd= and HealthInterval= (plus timeout/retries/start period where present).

api.imageConverted

Mapped to Image=

api.container_nameConverted

Mapped to ContainerName=

api.environmentConverted

1 variable(s) converted to Environment=

api.networksConverted

2 network(s) converted to Network=

api.restartConverted

Mapped to Restart=always

api.commandConverted

Mapped to Exec=

Next steps

  1. 1.Copy the generated files into ~/.config/containers/systemd/
  2. 2.Run: systemctl --user daemon-reload
  3. 3.Start each service: systemctl --user start <name>.service
  4. 4.Enable on boot: loginctl enable-linger $USER (needed for rootless services to survive logout)