Compose to Docker Run Converter
Turn compose services back into docker run commands with a flag-by-flag conversion report — entirely on this device.
One docker run command per service appears here as you paste.
Flag-by-flag conversion report
The flag map appears here after a conversion — every compose key, one row.
Press ? for shortcuts
Do it offline:$ docker compose config
How to use it
- Paste a compose file, load one from disk, or drop it on the input pane — conversion runs as you type, and YAML mistakes are anchored to their line with a caret frame.
- Read one card per service on the right: declared networks and volumes arrive first as create pre-lines, and services follow in depends_on order.
- Check the flag map under the panes — each compose key states its outcome, so anything a single container cannot express is a visible comment instead of a silent hole.
- Copy a single card, copy everything, or download docker-run.sh; switching to single-line layout makes the commands paste-safe for CI variables and Makefiles.
Sometimes you need a compose service back as a plain docker run command — on a box without a compose file, in a CI step, or to debug one container in isolation. Paste your docker-compose.yaml and each service becomes a ready-to-paste command, with a flag-by-flag map showing where every key went: an emitted flag, a comment, or a typed refusal. The file is parsed on this device and nowhere else.
Examples
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
environment:
- NGINX_HOST=example.com
- NGINX_PORT=80
volumes:
- webdata:/usr/share/nginx/html
volumes:
webdata: {}
docker volume create webdata
docker run \
-p 8080:80 \
-e NGINX_HOST=example.com \
-e NGINX_PORT=80 \
-v webdata:/usr/share/nginx/html \
nginx:1.27-alpine
The declared volume turns into a create pre-line you run once — the chip reads: Declared volumes become "docker volume create" pre-lines: webdata. Ports, list-form environment entries, and the mount all land as flags.
services:
db:
image: postgres:16.3-alpine
container_name: maindb
restart: unless-stopped
mem_limit: 512m
environment:
POSTGRES_USER: app
POSTGRES_DB: appdb
docker run \
--name maindb \
--restart unless-stopped \
-e POSTGRES_USER=app \
-e POSTGRES_DB=appdb \
-m 512m \
postgres:16.3-alpine
Map-form environment converts the same way list form does, and mem_limit rides -m with your suffix intact — 512m is the limit docker compose config normalizes to the byte string 536870912, so both spellings mean one thing.
services:
worker:
image: python:3.12-alpine
command: ["python", "worker.py"]
volumes:
- /var/lib/appdata
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
docker run \
-v /var/lib/appdata \
--cpus 0.5 \
--memory 256M \
python:3.12-alpine \
python worker.py
An anonymous volume stays anonymous — a bare container path, no name invented for it. The deploy resource limits become --cpus and --memory, and the exec-form command trails the image as plain arguments.
services:
frontend:
image: nginx:1.27-alpine
ports:
- "8082:80"
depends_on:
- api
api:
image: node:22-alpine
command: ["node", "api.js"]
init: true
# init: not converted by this tool — kept as this comment so nothing is silently dropped.
docker run \
node:22-alpine \
node api.js
# depends_on: no docker run flag — the commands below are emitted in dependency order instead.
docker run \
-p 8082:80 \
nginx:1.27-alpine
api prints before frontend because frontend depends on it — the chip reads: Commands are emitted in depends_on order: api → frontend. The init key has no flag here, so it survives as a comment and a report row rather than vanishing.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Compose networking does not come along by itself
Services in one compose file share a default network where each container resolves the others by name; plain docker run joins the legacy bridge, which has no such name resolution. When your containers must talk, create a network once and append --network to each command — the tool only emits create pre-lines for networks the file actually declares.
Relative bind mounts change their anchor
A source like ./html is resolved by compose against the directory holding the YAML file, while docker run resolves it against wherever you happen to be standing. The path is emitted verbatim, so run the commands from the compose file's directory or swap in an absolute path.
Byte sizes are one number in two spellings
Writing mem_limit: 512m and mem_limit: 536870912 configures the identical limit — the suffix form is multiplied out by powers of 1024. The emitted -m keeps whichever spelling you wrote, since docker run accepts both.
Variables are not interpolated for you
Compose substitutes ${VAR} from your environment or an .env file before containers start. This converter emits your text verbatim inside single quotes, so no shell expands it behind your back — resolve placeholders first (docker compose config does exactly that) when you want literal values.
One network at create time, the rest by connect
A service attached to several networks gets its first one as --network on the command; every further attachment appears as a docker network connect comment to run after the container starts. That keeps the emitted line valid on every docker release rather than only recent ones.
Common errors
Flow sequence in block collection must be sufficiently indented and end with a ] at line 4, column 1:- The YAML parser refuses before any conversion is attempted, and its position lands in a caret frame beside your input. An opening bracket without its closing partner — usually a clipped paste — reads as an unterminated flow sequence.
This does not parse as a compose file — the top level is a string scalar, and a compose file is a YAML mapping with a services: key.- Dockerfiles trip this one: FROM node:22 is a valid YAML scalar, not a mapping. A Dockerfile describes how an image is built; this page wants the file that describes how containers run.
No services found — a compose file declares each container under a top-level services: mapping.- A file holding only volumes:, networks:, or an empty services: block gives the converter nothing to emit. Indentation slips cause most of these — a service accidentally outdented to the top level stops being a service.
FAQ
Why do I get one docker run command per service?
Because docker run starts exactly one container, while a compose file orchestrates several. The tool keeps that honest: each service becomes its own command, emitted in depends_on order, with shared networks and volumes hoisted into create pre-lines you run once.
What happens to keys docker run cannot take?
Every present key gets exactly one row in the flag map, so the ones without a flag become comments above their command — profiles-gated services are skipped by name, and a value carrying a control character is refused outright with the reason stated.
Can I go the other way, from docker run to compose?
Yes — that direction is its own page, the Docker Run to Compose Converter, which turns commands into a compose.yaml with the same nothing-dropped reporting. Each page owns one direction so neither needs a mode switch.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Convert the compose file again.
- ⌘/Ctrl + S
- Download docker-run.sh instead of saving the page.
- ⌘/Ctrl + C
- Copy every command while nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy every command even mid-selection.
- t
- Advance the theme.
- ?
- Open the shortcut help overlay.
- Esc
- Dismiss the topmost overlay.