← Back to Blog

Convert docker run to docker-compose.yml (Step-by-Step)

You started a container with a docker run command. Now you need to check it into version control, share it with your team, or add it to a multi-service stack. Converting it to a docker-compose.yml file is the right move - here is the complete flag-by-flag mapping.

Why Move from docker run to docker-compose

A single docker run command gets unwieldy fast. Add ports, volumes, environment variables, a restart policy, and a custom network, and you have a command that wraps across multiple lines and is impossible to remember. Problems with relying on docker run:

  • Not version-controllable as a single, readable file
  • Difficult to share with team members or document
  • No built-in dependency ordering between containers
  • Must be re-typed or stored in a shell script
  • Does not integrate cleanly with CI/CD pipelines

docker-compose.yml solves all of these. It is declarative, readable, version-controlled YAML that describes your entire service configuration in one place.

The Basic Structure of docker-compose.yml

Before mapping flags, understand the YAML structure:

version: "3.9"          # Compose file format version

services:               # Define one or more services
  my-service:           # Service name (replaces --name)
    image: nginx:latest # The Docker image
    # ... all other config goes here

volumes:                # Named volumes (optional)
  my-data:

networks:               # Custom networks (optional)
  my-network:

Step-by-Step: Real Example Conversion

Let's convert a real-world docker run command for a web application with a database:

docker run -d \
  --name my-app \
  -p 3000:3000 \
  -e NODE_ENV=production \
  -e DATABASE_URL=postgres://user:pass@db:5432/myapp \
  -v /host/uploads:/app/uploads \
  --restart unless-stopped \
  --network app-network \
  my-org/my-app:latest

The equivalent docker-compose.yml:

version: "3.9"

services:
  my-app:
    image: my-org/my-app:latest
    container_name: my-app          # --name
    ports:
      - "3000:3000"                 # -p 3000:3000
    environment:
      NODE_ENV: production          # -e NODE_ENV=production
      DATABASE_URL: postgres://user:pass@db:5432/myapp
    volumes:
      - /host/uploads:/app/uploads  # -v
    restart: unless-stopped         # --restart unless-stopped
    networks:
      - app-network                 # --network

networks:
  app-network:
    external: true                  # use existing network

Complete Flag Mapping Cheat Sheet

-p / --publish: Port Mapping

# docker run
-p 80:80
-p 127.0.0.1:8080:8080   # bind to specific interface
-p 3000                   # random host port
# docker-compose.yml
ports:
  - "80:80"
  - "127.0.0.1:8080:8080"
  - "3000"                 # random host port

-v / --volume: Volume Mounts

# docker run
-v /host/path:/container/path         # bind mount
-v /host/path:/container/path:ro      # read-only
-v myvolume:/container/path           # named volume
# docker-compose.yml
volumes:
  - /host/path:/container/path
  - /host/path:/container/path:ro
  - myvolume:/container/path

# Named volume must also be declared at top level
volumes:
  myvolume:

-e / --env: Environment Variables

# docker run
-e KEY=value
-e KEY                     # inherit from host shell
--env-file .env
# docker-compose.yml
environment:
  KEY: value
  KEY:                     # inherit from host shell (no value)

# Or use env_file
env_file:
  - .env

--name: Container Name

# docker run
--name my-container
# docker-compose.yml
container_name: my-container

--restart: Restart Policy

# docker run options
--restart no               # default
--restart always
--restart unless-stopped
--restart on-failure
--restart on-failure:3     # max 3 retries
# docker-compose.yml
restart: no
restart: always
restart: unless-stopped
restart: on-failure

--network: Networks

# docker run
--network my-network
--network host
--network none
# docker-compose.yml
networks:
  - my-network

# Top-level networks section
networks:
  my-network:              # creates new network
    driver: bridge

  existing-network:
    external: true         # use pre-existing network

-d / --detach: Background Mode

The -d flag has no equivalent in docker-compose because docker compose up -d runs all services detached by default.

--entrypoint: Override Entrypoint

# docker run
--entrypoint /bin/sh
# docker-compose.yml
entrypoint: /bin/sh
# Or as array:
entrypoint: ["/bin/sh", "-c"]

Command Override (positional argument after image)

# docker run
docker run nginx:latest nginx -g "daemon off;"
# docker-compose.yml
command: nginx -g "daemon off;"
# Or as array:
command: ["nginx", "-g", "daemon off;"]

--user: Run as Specific User

# docker run
--user 1000:1000
--user www-data
# docker-compose.yml
user: "1000:1000"

--hostname: Container Hostname

# docker run
--hostname mycontainer
# docker-compose.yml
hostname: mycontainer

--add-host: /etc/hosts Entries

# docker run
--add-host host.docker.internal:host-gateway
# docker-compose.yml
extra_hosts:
  - "host.docker.internal:host-gateway"

--cap-add / --cap-drop: Linux Capabilities

# docker run
--cap-add NET_ADMIN
--cap-drop ALL
# docker-compose.yml
cap_add:
  - NET_ADMIN
cap_drop:
  - ALL

Convert docker run to Compose Automatically

Paste your docker run command and get a ready-to-use docker-compose.yml in seconds. Free, no install required.

Open Docker to Compose Tool

Multi-Container Example: App + Database + Redis

The real power of Compose is managing multiple containers together. Here is a complete example for a Node.js app with PostgreSQL and Redis:

version: "3.9"

services:
  app:
    image: my-org/my-app:latest
    container_name: app
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://appuser:secret@db:5432/appdb
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - app-net

  db:
    image: postgres:15-alpine
    container_name: postgres
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - app-net

  redis:
    image: redis:7-alpine
    container_name: redis
    volumes:
      - redis-data:/data
    restart: unless-stopped
    networks:
      - app-net

volumes:
  pg-data:
  redis-data:

networks:
  app-net:
    driver: bridge

Secrets and Sensitive Environment Variables

Never hardcode sensitive values in docker-compose.yml. Use a .env file at the same level as the compose file:

# .env file (add to .gitignore)
POSTGRES_PASSWORD=my-secret-password
API_KEY=sk-prod-abc123
# docker-compose.yml references .env automatically
services:
  db:
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  app:
    environment:
      API_KEY: ${API_KEY}

You can also use Docker Secrets for production Swarm deployments, which mounts secrets as files in /run/secrets/ rather than environment variables.

Useful docker compose Commands

# Start all services in background
docker compose up -d

# Start and rebuild images
docker compose up -d --build

# Stop and remove containers
docker compose down

# Stop, remove containers, AND delete volumes
docker compose down -v

# View logs for all services
docker compose logs -f

# View logs for a specific service
docker compose logs -f app

# Exec into a running container
docker compose exec app sh

# Scale a service
docker compose up -d --scale app=3

FAQ

What version should I use in docker-compose.yml?

For new projects, use version: "3.9". It supports all modern features including healthchecks, deploy configs for Swarm, and depends_on with conditions. If you are not using Swarm, the version key is actually optional in Compose v2 (the current default) - but including it is good practice for clarity.

What is the difference between docker-compose and docker compose?

docker-compose (hyphen) is the legacy Python-based standalone binary (v1). docker compose (space) is the modern Go plugin built into Docker Desktop and Docker Engine (v2). The YAML format is the same between both. The v2 plugin is the current standard and should be used for all new projects.

How do I handle depends_on properly?

depends_on alone only waits for the container to start, not for the service inside it to be ready. For a database, the container starts before PostgreSQL finishes initializing. Use depends_on with condition: service_healthy and define a healthcheck on the dependency service. This ensures your app container only starts once the database is actually accepting connections.

Can I use a Dockerfile instead of an image?

Yes. Replace image: with a build: section pointing to your Dockerfile's directory. Run docker compose up -d --build to rebuild the image when your Dockerfile or source changes.

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        NODE_VERSION: "20"

How do I mount configuration files into a container?

Use a bind mount with a specific file path. This is more precise than mounting an entire directory and avoids accidentally overwriting container files:

volumes:
  - ./nginx.conf:/etc/nginx/nginx.conf:ro
  - ./certs:/etc/nginx/certs:ro

Use our free tool here → Docker Run to Compose Converter

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.