From docker run to Compose: flags that map cleanly (and those that don’t)
Published: 2026-09-05
Which docker run flags become Compose YAML fields, which need manual edits (--rm, --mount, --gpus, health checks), and how to convert locally without uploading your command.
docker run is how you try an image once. Compose is how you declare the same container as a reusable service next to networks, volumes, and other apps. Turning a long docker run line into a services: fragment saves time—but only if you know which flags map 1:1 and which only look equivalent.
Paste commands that contain registry tokens, AWS_* env vars, or host paths carefully. Prefer a converter that runs in your browser so the command never hits someone else’s server. See Why “local only” matters for developer tools.
What you are converting into
A typical one-liner:
docker run -d --name web -p 8080:80 \
-e NGINX_HOST=example.com \
-v web-data:/usr/share/nginx/html:ro \
--restart unless-stopped \
nginx:alpine
becomes a Compose service under services: (Compose Spec often omits a top-level version:; older files still use version: "3.8"). The image, ports, env, bind/named volumes, restart policy, and optional container_name usually transfer cleanly. Detach (-d) is implied by Compose’s lifecycle—you do not need an equivalent key.
Service names are often derived from --name or the image repository (strip registry path and tag). Override the name when the derived slug is awkward or collides with another service.
Flags that map cleanly
docker run |
Compose field (typical) |
|---|---|
image + optional args after -- |
image, command |
-p / --publish |
ports |
--expose |
expose |
-e KEY=val / --env |
environment |
-v / --volume |
volumes (short syntax) |
--name |
container_name (+ service key) |
--restart |
restart |
-w / --workdir |
working_dir |
-u / --user |
user |
--entrypoint |
entrypoint |
--hostname / -h |
hostname |
--network / --net |
networks (plus top-level networks:) |
-i / -t / -it |
stdin_open, tty |
--privileged, --read-only, --init |
matching booleans |
--platform |
platform |
--device, --cap-add, --cap-drop |
devices, cap_add, cap_drop |
--add-host, --dns, --tmpfs |
extra_hosts, dns, tmpfs |
--shm-size, --pull, --stop-signal |
shm_size, pull_policy, stop_signal |
-m / --memory, --cpus |
often deploy.resources.limits |
Short clusters such as -dit expand the same way as separate flags. Quoted values and line continuations (\) are normal in pasted shell history; treat the result as a draft, then run docker compose config before production.
Flags that do not map 1:1
These show up constantly in READMEs and still need human Compose edits:
| Flag / pattern | Why it is awkward |
|---|---|
--rm |
No Compose twin; Compose already manages create/remove with the project. |
-P / --publish-all |
Publishes every EXPOSEd port; Compose wants explicit ports: mappings. |
--env-file |
Path is not inlined; add env_file: yourself (and keep real secrets out of Git—see .env files). |
--mount |
Richer than -v; converters often leave a note—prefer short -v or hand-write long volumes: / mount objects. |
--gpus |
Needs Compose deploy / device request shapes that vary by engine; add manually. |
Health-check flags (--health-cmd, …) |
Map to a healthcheck: block; not always auto-emitted. |
--ulimit, --sysctl, --security-opt, --group-add |
Engine-specific; usually manual. |
--log-driver / --log-opt |
Logging config belongs under Compose logging: when you need it. |
--link |
Legacy; use user-defined networks and service DNS names instead. |
--cpuset-cpus, --cpu-shares / -c |
Not the same as simple cpus limits; review before copying. |
Unrecognized or exotic flags should appear as notes in the output, not silent drops. If a note is missing, assume the converter ignored it and check the original command.
Compose Spec vs version: "3.8"
Modern Compose files often use the Compose Specification without a version key. Some older tooling and tutorials still expect version: "3.8". The YAML under services: is largely the same for day-to-day fields; deploy.resources behavior can differ between Swarm and plain docker compose on a single host—verify limits where you actually run.
Custom networks named in --network may be declared at the top level. Special names like host, bridge, and none are often marked external because Compose does not create them the way it creates a project network.
A practical conversion workflow
- Copy the
docker run(ordocker container run) from docs, a script, or shell history. - Convert locally to a services fragment; pick Compose Spec or v3.8 style to match your repo.
- Read every warning comment the tool emits (
--rm,--env-file,--mount, GPUs, health checks). - Move secrets from inline
-einto Composeenv_fileor your secret store; do not commit real credentials. - Align volume paths and network names with the rest of the stack; replace
--linkwith service names on a shared network. - Validate with
docker compose config(or your CI equivalent), then bring the stack up.
For firewall and published ports after you settle on mappings, Common TCP/UDP ports and when to open them pairs well with Compose ports:.
Try it locally in your browser
Use the docker run to Compose converter to:
- Paste a
docker runcommand and get a Compose YAML fragment. - Map common flags (
-p,-e,-v,--name,--restart,-it, networks, memory/cpus, and more). - See notes for flags that need manual Compose edits (
--rm,--env-file,--mount,--gpus, health checks). - Choose Compose Spec (no
version) or Compose v3.8 output—parsed entirely in your tab.
Related reading
- Writing a good .gitignore: presets, negation, and what not to ignore — keep
.envand local overrides out of Git when you addenv_file. - .env files: syntax, merging, and secrets hygiene — safer than baking secrets into
docker run -e. - Converting curl to JavaScript fetch — same “flags that map / don’t” mindset for HTTP one-liners.
- Why “local only” matters for developer tools — why paste-heavy shell commands belong in-tab.