Skip to main content

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.

tip

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.
  • Environment & secrets

    • 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.
caution

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.