docker run to docker-compose: The Complete Migration Guide (with Flag Map)
Why bother moving docker run to compose
A service launched with docker run -d --name web -p 8080:80 -v ... keeps its configuration only in the command you typed and your memory. Rebuild on another machine, six months later, or hand over to a colleague — you're archaeology-ing through shell history. Compose turns all of it into a version-managed YAML file:
| Raw docker run | docker compose |
|---|---|
| Config lives in shell history | Config lives in docker-compose.yml, tracked in git |
| Multi-container startup order by hand | depends_on declares it |
| Rebuild = rm + retype the long command | docker compose up -d is idempotent |
| Half-stopped services cleaned from memory | docker compose down cleans everything |
| Ports/volumes scattered | One file shows the whole picture |
Convert in thirty seconds
Paste the docker run command into the docker run to Compose converter, save the result, and run:
docker compose up -d
The conversion runs entirely in your browser — commands containing passwords or internal hosts never leave your machine.
The flag-by-flag map
| docker run | docker-compose.yml | Notes |
|---|---|---|
| nginx:1.25 | image: nginx:1.25 | image |
| --name web | container_name: web (or omit for service name) | Omitting is preferred |
| -p 8080:80 | ports: ["8080:80"] | quote port pairs in YAML |
| -v ./site:/usr/share/nginx/html | volumes: ["./site:/usr/share/nginx/html"] | bind mount |
| -v cache:/var/cache | volumes: ["cache:/var/cache"] | named volume (declare at top level too) |
| -e TZ=UTC | environment: ["TZ=UTC"] | env var |
| --env-file .env | env_file: [.env] | env file |
| --restart always | restart: always | restart policy |
| --network mynet | networks: [mynet] + top-level block | custom network |
| --network host | network_mode: host | special networks use network_mode |
| -u 1000:1000 | user: "1000:1000" | run user |
| -w /app | working_dir: /app | working dir |
| --entrypoint "sh -c ..." | entrypoint: ... | entrypoint |
| -m 512m | mem_limit: 512m | memory limit |
| --cpus 1.5 | cpus: "1.5" | CPU limit |
| command after image | command: ... | container command |
Three classic pitfalls
1. Bare -e passthrough can't convert
docker run -e DEBUG nginx # references the HOST's DEBUG variable
A compose file is static and can't express "reference a host variable". Write it fully as -e DEBUG=value, or move everything to env_file.
2. Anonymous volumes have no equivalent
docker run -v /var/log nginx (container path only) creates an anonymous volume; compose needs a named volume or a bind path. The converter lists it in the warnings area — give it a name (logdata:/var/log) and declare it in the top-level volumes block.
3. --rm changes meaning
In raw run, --rm means "delete the container on exit"; compose manages the lifecycle via down and neither needs nor supports --rm. Not a lost feature — a different model.
Multi-container: wire dependencies after converting
After converting each docker run into a service, add the dependency graph:
services:
app:
image: myapp:2.0
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
The service_healthy condition (compose v2.20+) solves the classic "app starts before the database is ready" race — the problem raw run solved with sleep scripts.
FAQ
Q: Does the generated file need a version field? No. Modern docker compose (v2 CLI) deprecates version and warns when present. The converter emits the modern format.
Q: Must the file be named docker-compose.yml?
compose.yaml / docker-compose.yml are picked up by default; use -f for anything else: docker compose -f prod.yml up -d.
Q: Will up conflict with already-running containers?
Yes — same-name conflicts (container name already in use, see this guide). docker stop/rm the old container first, or rename the service for transition. Port conflicts: port already allocated.
Provided by ToolVault. Related tools: docker run to Compose converter, Dockerfile Generator, Linux Cheatsheet. Related reading: Container name conflicts, Port already allocated. See the homepage for more developer tools.
Related Tools
Related Articles
Permission denied (publickey): 6 Reasons Git Push Fails Over SSH (and the Fix for Each)
git clone or push rejected with Permission denied (publickey) fatal: Could not read from remote repository? Covers missing keygen, key not loaded in the agent, public key not added to GitHub/GitLab, multi-account key routing with ~/.ssh/config, deploy key limits, and wrong remote URLs — with ssh -v diagnostics.
error:0308010C digital envelope routines::unsupported — Fixing Node 17+ Breakage in Older Projects
Node 17+ crashes webpack 4 builds with error:0308010C:digital envelope routines::unsupported because OpenSSL 3.0 removed MD4. Three fixes compared: --openssl-legacy-provider quick unblock, upgrading to webpack 5 as the real fix, and pinning Node 16 as a stopgap.
ECONNREFUSED: Connection Refused — 5 Causes Explained (Including Docker)
Node, Java, or curl reporting connect ECONNREFUSED 127.0.0.1:3306? It means nothing is listening on that port. Covers service not running, wrong port, 127.0.0.1-only binding, Docker container networking, and firewall REJECT rules — with ss/lsof diagnostic commands.