🍱 Lunchbox Hands

docker

docker run to docker-compose: Convert Any Command, Flag by Flag

A practical guide to converting a docker run command into a docker-compose.yaml file — what each flag maps to (-p, -e, -v, --restart, --name, --network), with copy-paste examples for Postgres, Redis, and Nginx.

You found a docker run one-liner in a README, it works, and now you want it in a docker-compose.yaml so it’s version-controlled, self-documenting, and starts with one command. The translation is mechanical once you know which flag becomes which Compose key. This guide is that mapping — plus three real examples you can copy straight into a file.

The flag-to-Compose cheat sheet

Every common docker run flag has a direct home in Compose:

docker run flagCompose keyNotes
--name webcontainer_name: webUsually unnecessary — the service name already names it
-p 8080:80ports: ["8080:80"]host:container
-e KEY=valenvironment: { KEY: val }Or an env_file
-v /data:/var/libvolumes: ["/data:/var/lib"]Bind mount or named volume
--restart unless-stoppedrestart: unless-stoppedSame values
--network mynetnetworks: [mynet]Define the network at the bottom
-d(nothing)Compose runs detached with up -d
--rm(nothing)Not a Compose concept — it’s for one-off runs
-itstdin_open: true + tty: trueRarely needed for services
image:tag (last arg)image: image:tagThe image is a key, not positional

Two of these trip people up: -d and --rm have no Compose equivalent because they describe how you invoke a run, not what the service is. You get detached mode from docker compose up -d at the command line.

Worked example 1: Postgres

docker run --name db -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=app \
  -p 5432:5432 -v pgdata:/var/lib/postgresql/data \
  --restart unless-stopped -d postgres:16

becomes:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  pgdata:

Note the named volume pgdata has to be declared in the top-level volumes: block — that’s the one thing the CLI creates implicitly that Compose makes you spell out.

Worked example 2: Redis

docker run --name cache -p 6379:6379 --restart always -d redis:7-alpine

becomes:

services:
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    restart: always

Worked example 3: Nginx with a config bind mount

docker run --name proxy -p 80:80 -p 443:443 \
  -v ./nginx.conf:/etc/nginx/nginx.conf:ro \
  -v ./certs:/etc/nginx/certs:ro \
  --restart unless-stopped -d nginx:1.27

becomes:

services:
  proxy:
    image: nginx:1.27
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    restart: unless-stopped

The :ro suffix (read-only) carries over unchanged — it’s part of the volume string in both syntaxes.

Gotchas worth knowing

  • Port order is host:container in both — get it backwards and the mapping silently does the wrong thing.
  • Named volumes must be declared; bind mounts (a path starting with . or /) don’t.
  • Multiple -e flags collapse into one environment: map. For long lists, point env_file: at a .env file instead — noting that Compose’s .env dialect interpolates ${VAR} and other loaders don’t, one of several disagreements between .env parsers.
  • --link is dead — don’t translate it. Services on the same Compose network reach each other by service name automatically.

Do it automatically

Once you’ve seen the pattern a few times you’ll do simple commands in your head — but for anything with a dozen flags, paste the whole docker run command into the Docker Run → Compose converter and get the docker-compose.yaml instantly. It maps ports, env, volumes, name, restart, and networks for you, and it runs entirely in your browser — nothing is sent anywhere.

And while you’re tidying the run half of the workflow, the build half has a footgun of its own: .dockerignore doesn’t match patterns the way .gitignore does, and the .dockerignore generator writes a stack-aware starting point.