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.
How QuadletGen works
- 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
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
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
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
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
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.
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)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)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 key | Quadlet key |
|---|---|
| image | Image= |
| container_name | ContainerName= |
| ports | PublishPort= |
| environment | Environment= |
| volumes (bind mount) | Volume=/host/path:/container/path |
| volumes (named) | Volume=name.volume:/container/path |
| networks | Network=name.network |
| depends_on | After= / Requires= |
| restart | Restart= |
| command | Exec= |
| healthcheck.test | HealthCmd= |
| healthcheck.interval | HealthInterval= |
| build | (not converted) |
| secrets | (not converted) |
When to use QuadletGen
| Scenario |
|---|
| Migrating a homelab stack (Jellyfin, Nextcloud, Immich) to Podman |
| Deploying a project's README compose file on a Podman/RHEL host |
| Learning Quadlet INI syntax from a file you already know |
| Checking whether a stack has unsupported build or secrets sections |
| Deciding between rootless and rootful before deploying |
| Standardizing a fleet of RHEL servers on systemd-managed containers |
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.
# Rootless
mkdir -p ~/.config/containers/systemd/
cp *.container *.volume *.network ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start web.serviceHow is QuadletGen different from podlet?
| podlet (CLI) | QuadletGen | |
|---|---|---|
| Setup required | Rust toolchain or binary install | None, runs in browser |
| Variable substitution | Hard-fails on unresolved variables | Converts what it can, flags the rest |
| Output | Single stdout stream to split by hand | Per-file tabs plus zip download |
| Coverage report | None | Per-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.