How I turn a long docker run command into Docker Compose
· 5 min read
A docker run command is fine when it is short. But after a few ports, volumes, and
environment variables, it becomes hard to read and easy to break.
You may see something like this:
docker run -d \
--name app \
-p 8080:80 \
-e NODE_ENV=production \
-e DATABASE_URL=postgres://user:pass@db:5432/app \
-v app-data:/var/lib/app \
--restart unless-stopped \
nginx:alpine
It works, but it is not pleasant to maintain. The
Docker Run to Compose tool helps convert that kind
of command into a docker-compose.yml service.
Why convert to Compose?
I convert long docker run commands to Compose for one main reason: I want the
configuration to live in a file.
A file is easier to:
- read later;
- commit to Git;
- review with someone else;
- edit without rebuilding a long shell command;
- reuse after a server reboot or migration.
docker run is a command. Compose is documentation that can run.
Step 1: start with the original command
Copy the full docker run command. Try to include every line if it is split with
backslashes.
For example:
docker run -d \
--name my-nginx \
-p 8080:80 \
-v ./html:/usr/share/nginx/html:ro \
--restart unless-stopped \
nginx:alpine
Open Docker Run to Compose and paste it into the input.
The tool will parse the flags and build a Compose service from them.
Step 2: understand the image
The image is usually the last part of the command:
nginx:alpine
In Compose, that becomes:
services:
my-nginx:
image: nginx:alpine
The image is the container template. It tells Docker what to run.
Step 3: understand the container name
This flag:
--name my-nginx
usually becomes:
container_name: my-nginx
Compose services also have their own service name:
services:
my-nginx:
In many cases, you do not strictly need container_name. Compose can generate a
name. But if the original command used a name and other scripts depend on it, keep
it.
Step 4: understand port mapping
This flag:
-p 8080:80
means:
host port 8080 -> container port 80
In Compose:
ports:
- "8080:80"
Plain language:
When someone opens port
8080on the server, send the traffic to port80inside the container.
The left side is outside the container. The right side is inside the container.
This is one of the most common places to get confused.
Step 5: understand volumes
This flag:
-v ./html:/usr/share/nginx/html:ro
becomes:
volumes:
- ./html:/usr/share/nginx/html:ro
Read it as:
host path : container path : mode
So:
./htmlis on the host machine;/usr/share/nginx/htmlis inside the container;romeans read-only.
If the volume is named instead of a path:
-v app-data:/var/lib/app
then Compose may also need a top-level volume declaration:
volumes:
app-data:
Named volumes are managed by Docker. Bind mounts point to a real host path.
Step 6: understand environment variables
These flags:
-e NODE_ENV=production
-e LOG_LEVEL=info
become:
environment:
NODE_ENV: production
LOG_LEVEL: info
Environment variables are configuration values passed into the container.
Be careful with secrets. A Compose file can contain secrets, but that does not mean
it should be committed to a public repository. For sensitive values, prefer an .env
file, a secret manager, or deployment environment variables.
Step 7: understand restart policy
This flag:
--restart unless-stopped
becomes:
restart: unless-stopped
This tells Docker to restart the container if it exits, unless you stopped it manually.
For small servers, I often use unless-stopped for long-running services. It keeps
basic services alive after a reboot.
Step 8: understand detached mode
This flag:
-d
means detached mode. It tells Docker to run the container in the background.
In Compose, this is not usually written inside the YAML. Instead, it becomes part of the command you run:
docker compose up -d
So if the converter does not put -d in the YAML, that is normal. -d is how you
start Compose, not a service property.
Step 9: review the generated Compose file
The generated output may look like this:
services:
my-nginx:
image: nginx:alpine
container_name: my-nginx
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped
Read it from top to bottom. Do not copy it blindly. Check that every important part from the original command is represented.
Step 10: save and run it
Create a file named:
docker-compose.yml
or, with newer Compose:
compose.yml
Then start it:
docker compose up -d
Check the running container:
docker compose ps
View logs:
docker compose logs -f
Stop it:
docker compose down
What may need manual cleanup
A converter can parse the command, but it cannot always know your intent.
After conversion, I usually review these:
- service name: is it clear?
- container name: do I really need it?
- ports: are any ports exposed publicly by mistake?
- volumes: are host paths correct relative to the Compose file?
- environment variables: should secrets move to
.env? - restart policy: should this service restart automatically?
- network: does it need to talk to other services?
The generated Compose file is a strong starting point, not a replacement for review.
A small before and after
Before:
docker run -d --name redis -p 6379:6379 --restart unless-stopped redis:7-alpine
After:
services:
redis:
image: redis:7-alpine
container_name: redis
ports:
- "6379:6379"
restart: unless-stopped
Run it with:
docker compose up -d
My checklist
When converting docker run to Compose, I check:
- Is the image correct?
- Did the container name become the right service name?
- Are port mappings in the right direction?
- Are volumes pointing to the correct host paths?
- Are environment variables present?
- Are secrets handled safely?
- Is the restart policy correct?
- Can I start it with
docker compose up -d? - Can I see logs with
docker compose logs -f?
The main benefit is not just prettier YAML. The real benefit is that the container configuration becomes something you can keep, review, and run again later.
Comments
Comments are welcome — please read the comment policy first. Powered by giscus and GitHub Discussions.