Both docker run and docker compose can create and start Docker containers, but they are designed for different workflows.
docker run describes a container directly on the command line. Docker Compose moves that configuration into a reusable YAML file and lets you manage the application as a project.
For a quick experiment or a disposable container, docker run is often the fastest choice. For an application whose configuration should be repeatable, reviewable and easy for other developers to start, Docker Compose is usually the better fit.
Docker run vs Docker Compose at a glance
| Docker run | Docker Compose |
|---|---|
| Configuration is supplied as CLI arguments. | Configuration is stored in compose.yaml. |
| Excellent for quick, one-off containers. | Excellent for repeatable application environments. |
| Starts one container per command. | Can define one service or an entire multi-container application. |
| Long configurations can become difficult to reproduce. | Configuration can be committed to source control and reviewed. |
| You manage related containers individually. | Compose manages related services as one project. |
| No application-level dependency model. | Supports dependencies, health checks, networks, volumes, secrets and more. |
A simple docker run example
Suppose you want to serve files from a local website directory with Nginx. A single docker run command is perfectly reasonable:
docker run -d --rm \
--name my-website \
--cpus 1.5 \
--memory 2g \
-p 8080:80 \
-v ./website:/usr/share/nginx/html:ro \
nginx:latestThis command creates a container named my-website, limits its CPU and memory, publishes Nginx on port 8080 and mounts the local website directory as read-only content.
For an experiment, tutorial or temporary local server, there is nothing wrong with this approach. The problem appears when the command becomes part of an application that must be started repeatedly or shared with a team. Every option now has to be remembered, documented or copied somewhere.
The same container with Docker Compose
Docker Compose moves those runtime settings into a declarative file. Create a file named compose.yaml:
services:
web:
image: nginx:latest
container_name: my-website
cpus: 1.5
mem_limit: 2g
ports:
- "8080:80"
volumes:
- ./website:/usr/share/nginx/html:roThen start the application with:
docker compose up -dThe result is essentially the same Nginx container, but the configuration is now saved in a file that can be reused, reviewed and placed in source control.
Use docker compose, not docker-compose
Older tutorials use the hyphenated docker-compose command. That syntax belongs to the original Python-based Docker Compose v1 implementation.
Modern Compose is integrated into the Docker CLI, so the command has a space:
docker compose up
docker compose down
docker compose ps
docker compose logsIf you maintain older scripts that call docker-compose, migrate them to the modern docker compose syntax.
Do modern Compose files need a version?
No. A modern compose.yaml should not begin with declarations such as version: "3.9".
Current Docker Compose follows the rolling Compose Specification. The old 2.x and 3.x Compose file formats were merged into that specification, and the top-level version property is now obsolete.
This is all you need to begin a normal Compose file:
services:
web:
image: nginx:latestDocker Compose is not only for multiple containers
One common misconception is that docker run is for one container and Docker Compose is only for multiple containers.
Compose works very well for a single-container application. The real advantage is not the number of containers. It is the ability to describe the application's runtime configuration declaratively.
If your one-container application requires ports, environment variables, volumes, resource limits and restart behavior that must be reproduced consistently, a small compose.yaml can be much easier to maintain than a long shell command.
Where Compose becomes especially useful
The difference becomes more obvious when an application needs several services. Imagine an application with Nginx, Tomcat and PostgreSQL.
With docker run, each container requires a separate command and you must coordinate their networking and configuration yourself.
With Compose, the application can be described as a single project:
services:
proxy:
image: nginx:latest
ports:
- "8080:80"
depends_on:
app:
condition: service_started
app:
image: tomcat:latest
expose:
- "8080"
depends_on:
db:
condition: service_healthy
db:
image: postgres:18
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: change-me
volumes:
- postgres-data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
volumes:
postgres-data:One command now creates the services, default network and persistent volume:
docker compose up -dCompose also gives the services DNS names on the project's network. For example, the application can address the database by the service name db rather than hard-coding a container IP address.
depends_on does not automatically mean ready
Container startup order and application readiness are different things.
The short form of depends_on can ensure that one service is started before another, but a started database may still need several seconds before it can accept connections.
For dependencies that must actually be ready, add a healthcheck and use condition: service_healthy, as the PostgreSQL example above does.
This is a much more reliable pattern than simply assuming a container is ready because its process has started.
Useful Docker Compose commands
Once an application is defined in compose.yaml, the most useful commands include:
docker compose up -d
docker compose ps
docker compose logs
docker compose logs -f
docker compose restart
docker compose stop
docker compose start
docker compose downdocker compose up -d creates and starts the application's services in the background. ps shows their state, while logs -f follows their output.
docker compose stop stops containers without removing them. docker compose down stops the project and removes its containers and networks. Named volumes are retained unless you explicitly request their removal.
When should you use docker run?
Use docker run when the container is temporary and the configuration is simple enough that you do not need to preserve it as part of an application definition.
Typical examples include:
- trying an image for the first time;
- starting a temporary web server;
- running a short-lived utility;
- debugging an image;
- demonstrating a Docker option on the command line; or
- running a disposable development experiment.
A long docker run command is not inherently wrong. The more important question is whether someone will need to reproduce that exact configuration later.
When should you use Docker Compose?
Use Docker Compose when the container configuration is part of the application rather than an incidental command.
Compose is especially useful when:
- developers need to reproduce the same environment;
- an application has several services;
- ports, volumes and environment variables must be documented;
- services have startup or readiness dependencies;
- persistent named volumes are required;
- the configuration belongs in source control;
- development and production need related configuration files; or
- you want to manage the application as a project instead of individual containers.
Docker run vs Docker Compose: the decision
The choice between docker run and Docker Compose is not really about which tool is more powerful. It is about whether the container is a one-off runtime operation or part of a repeatable application configuration.
For a quick container, start with docker run. For an application you expect to start again tomorrow, share with another developer or deploy consistently, put the configuration in compose.yaml and use docker compose.
That distinction scales from a single Nginx container on a developer laptop to a multi-service application with databases, persistent storage, health checks and explicit service dependencies.