Dockerfile vs Docker Compose
Dockerfile and Docker Compose solve different problems in the Docker ecosystem. "docker compose vs dockerfile" is a common question when you decide how to build and run your app:
- Dockerfile: Describes how to build a single image (build instructions)
- Docker Compose: Describes how multiple containers (services) run together (runtime composition)
This page explains the difference, shows a minimal example that uses both, and gives practical advice for typical developer workflows.
Concept explained
-
Dockerfile
- Purpose: produce a runnable image (layers, build context, commands).
- Used by: docker build, CI pipelines, image registries.
- Scope: one image at a time.
-
Docker Compose (YAML)
- Purpose: declare multiple services, volumes, networks, and how to run them together.
- Used by: docker compose up, local development, simple multi-container deployments.
- Scope: orchestration for a set of containers; can reference images or build from a Dockerfile.
Key point: Compose often uses Dockerfile to build each service's image, but you can also point Compose at prebuilt images.
Modern CLI: use docker compose
(space) from Docker CLI v2+. Old standalone docker-compose
may still exist but behaves similarly.
Step-by-step example
Goal: build a simple Node app image and run it with Postgres using Compose.
Project structure:
/example
├─ package.json
├─ app.js
├─ Dockerfile
└─ docker-compose.yml
Dockerfile:
FROM node:18-slim
WORKDIR /app
COPY package.json ./
COPY app.js ./
EXPOSE 3000
CMD ["npm", "start"]
docker-compose.yml:
services:
web:
build: . # builds using ./Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/app
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: password
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Build and run locally:
# build + start both services
docker compose up --build
# stop
docker compose down
If you only have the image and don't need to build locally:
services:
web:
# build: . # omit build step
image: myregistry/myapp:1.2.0 # this changes, now using a prebuilt image
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/app
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: password
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Variations & gotchas
-
Build vs image
- Compose
build:
triggers local image creation from a Dockerfile. - Compose
image:
pulls a remote image or uses an already-built local image.
- Compose
-
- Compose can set env vars inline, via env_file, or reference secrets (different behavior across Compose versions).
- For production secrets, use a secrets manager; don't store sensitive values in plain YAML or committed env files.
-
Networks and ports
- Compose creates a default network per project; services can talk using service names.
- Port mappings (host:container) are only for exposing services – internal communication uses the container port.
Compose v2 (the CLI subcommand docker compose
) differs from the older docker-compose
Python tool in some features and syntax. Check your environment.
Common mistakes
- Trying to use Compose as a build system only – Compose is primarily runtime orchestration.
- Committing secrets or database passwords into docker-compose.yml.
- Not mounting volumes for persistent data (e.g., databases) and losing data on restart.
- Expecting Compose to handle horizontal scaling and advanced orchestration like Kubernetes does.
- Binding host ports for every service in production (unnecessary and risky).
Best practices
- Keep Dockerfile focused and small (one concern: build image).
- Use Compose for local development and simple staging environments; reference immutable images for production deploys.
- Use volumes for data and bind-mounts only for development (code hot-reload).
- Use
.env
or env_file for non-sensitive config; use secret management for credentials. - Tag images and push to a registry for CI/CD; let deployments use tagged images, not local builds.
When to use / when not to use
When to use Dockerfile:
- You need reproducible image builds, CI integration, or to publish an image.
- App packaging and build optimization (multi-stage builds, caching layers).
When to use Docker Compose:
- Local development with multiple services (app, DB, cache).
- Quick integration testing or simple staging setups.
When not to use Compose:
- Large-scale production orchestration (use Kubernetes or a managed platform).
- When you need fine-grained autoscaling, advanced load balancing, or multi-node management.
Key takeaways
- Dockerfile = build instructions for one image; Compose = multi-container runtime configuration.
- Compose can call Dockerfile builds – they're complementary, not mutually exclusive.
- Use Dockerfile for CI/build, Compose for local dev and simple stacks.
- Protect secrets and persist data with volumes.
- Prefer tagged images from a registry for production deployments.