# How I turn a long docker run command into Docker Compose

A step-by-step beginner guide to reading a docker run command, understanding its flags, and converting it into a docker-compose service.

- Date: 2026-09-27
- URL: https://ilham.dev/posts/how-to-turn-docker-run-into-compose/
- Markdown: https://ilham.dev/posts/how-to-turn-docker-run-into-compose/index.md
- Tags: docker, devops, containers, tools
- Reading time: 5 min


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:

```sh
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](/tools/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:

```sh
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](/tools/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:

```sh
nginx:alpine
```

In Compose, that becomes:

```yaml
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:

```sh
--name my-nginx
```

usually becomes:

```yaml
container_name: my-nginx
```

Compose services also have their own service name:

```yaml
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:

```sh
-p 8080:80
```

means:

```text
host port 8080 -> container port 80
```

In Compose:

```yaml
ports:
  - "8080:80"
```

Plain language:

> When someone opens port `8080` on the server, send the traffic to port `80` inside
> 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:

```sh
-v ./html:/usr/share/nginx/html:ro
```

becomes:

```yaml
volumes:
  - ./html:/usr/share/nginx/html:ro
```

Read it as:

```text
host path : container path : mode
```

So:

- `./html` is on the host machine;
- `/usr/share/nginx/html` is inside the container;
- `ro` means read-only.

If the volume is named instead of a path:

```sh
-v app-data:/var/lib/app
```

then Compose may also need a top-level volume declaration:

```yaml
volumes:
  app-data:
```

Named volumes are managed by Docker. Bind mounts point to a real host path.

## Step 6: understand environment variables

These flags:

```sh
-e NODE_ENV=production
-e LOG_LEVEL=info
```

become:

```yaml
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:

```sh
--restart unless-stopped
```

becomes:

```yaml
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:

```sh
-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:

```sh
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:

```yaml
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:

```text
docker-compose.yml
```

or, with newer Compose:

```text
compose.yml
```

Then start it:

```sh
docker compose up -d
```

Check the running container:

```sh
docker compose ps
```

View logs:

```sh
docker compose logs -f
```

Stop it:

```sh
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:

```sh
docker run -d --name redis -p 6379:6379 --restart unless-stopped redis:7-alpine
```

After:

```yaml
services:
  redis:
    image: redis:7-alpine
    container_name: redis
    ports:
      - "6379:6379"
    restart: unless-stopped
```

Run it with:

```sh
docker compose up -d
```

## My checklist

When converting `docker run` to Compose, I check:

1. Is the image correct?
2. Did the container name become the right service name?
3. Are port mappings in the right direction?
4. Are volumes pointing to the correct host paths?
5. Are environment variables present?
6. Are secrets handled safely?
7. Is the restart policy correct?
8. Can I start it with `docker compose up -d`?
9. 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.
