Docker Compose vs Dockerfile: What’s the Real Difference?
Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.
If you’re new to containers, the confusion between a Dockerfile and Docker Compose is one of the most common stumbling blocks. They sound related, they live in the same directory half the time, and tutorials often mash them together without explaining the boundary. This article draws a hard line between the two so you know exactly which tool to reach for and when.
Short answer: a Dockerfile defines how to build a single container image. Docker Compose defines how to run one or more containers together as a stack, with networking, volumes, and environment variables already wired up. They’re not competitors — they’re different layers of the same workflow, and in any real project you’ll use both.
What Is a Dockerfile?
A Dockerfile is a plain-text script of instructions that Docker reads top to bottom to produce an image. Each instruction adds a layer: install a package, copy source code, set an environment variable, define the startup command. The output is a reusable, versioned artifact you can push to a registry and run anywhere Docker is installed.
Dockerfile Syntax Basics
Here’s a minimal Dockerfile for a Node.js API:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Every line here answers one question: what base image, what files, what dependencies, what command runs on container start. You build it with:
docker build -t myapp:latest .
And run the resulting image standalone:
docker run -p 3000:3000 myapp:latest
That’s the entire scope of a Dockerfile — it produces one image. It has no concept of other containers, networks between services, or persistent volumes shared across a stack. If your app needs a database, a cache, and a reverse proxy, a Dockerfile alone won’t wire those together.
What Is Docker Compose?
Docker Compose is an orchestration tool that reads a YAML file — typically docker-compose.yml or compose.yaml — and spins up multiple containers as a single, coordinated application. It handles networking between services automatically, manages named volumes, and lets you bring the whole stack up or down with one command.
docker-compose.yml Basics
Here’s a Compose file for that same Node app plus a Postgres database and Redis cache:
services:
api:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/appdb
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=appdb
volumes:
- db_data:/var/lib/postgresql/data
cache:
image: redis:7-alpine
volumes:
db_data:
Bring the entire stack up with:
docker compose up -d
Notice the build: . line under the api service — Compose still uses a Dockerfile to build that image. It doesn’t replace the Dockerfile; it consumes it as one ingredient in a larger recipe. The db and cache services skip a Dockerfile entirely and pull prebuilt images straight from Docker Hub.
Docker Compose vs Dockerfile: Key Differences
Here’s the distinction laid out plainly:
FROM, RUN, COPY, CMD). Compose uses YAML.db, cache, etc.).docker build / docker run operate on Dockerfiles and images. docker compose up / docker compose down operate on the whole stack defined in YAML.If you want the official word on syntax and supported fields, the Docker Compose file reference is the canonical source and worth bookmarking.
When to Use a Dockerfile Alone
Stick with just a Dockerfile when:
docker run directly.When to Use Docker Compose
Reach for Compose when:
This last case is extremely common for small-to-midsize deployments. If you’re self-hosting a stack — say a Node API, Postgres, and an Nginx reverse proxy — on a single server, Compose is usually the right level of complexity. We cover that exact setup in our guide on deploying a multi-container app with Docker Compose, and if you haven’t sorted out container-to-container networking yet, our Docker networking guide explains bridge networks, custom networks, and DNS resolution between services in more depth.
Using Dockerfile and Compose Together
In practice, almost every real-world project uses both. The Dockerfile defines how your application image is built; Compose defines how that image runs alongside its dependencies. Here’s a slightly more complete example with a build context and an Nginx reverse proxy:
services:
web:
build:
context: .
dockerfile: Dockerfile
expose:
- "3000"
restart: unless-stopped
proxy:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- web
restart: unless-stopped
Run docker compose up --build and Compose will build the web image from your Dockerfile, then start it alongside the Nginx proxy on a shared network. This pattern — one Dockerfile per custom service, one Compose file tying everything together — scales fine up to a handful of services before you’d start considering Kubernetes or Nomad.
Common Mistakes to Avoid
RUN commands in Compose files. Compose isn’t a build tool beyond invoking docker build; application-level install steps belong in the Dockerfile..env files or a secrets manager, and reference variables instead of committing credentials to version control.depends_on doesn’t wait for readiness. It only controls start order, not whether a service (like Postgres) is actually accepting connections yet — you often need a healthcheck or retry logic in your app.docker compose up is enough — you don’t need --build every time.docker-compose.override.yml or separate -f flags rather than maintaining divergent copies of the same file.Hosting Your Docker Compose Stack
Once your Compose file is solid, you need somewhere to run it. A small VPS with a couple of vCPUs and 2-4GB RAM is plenty for most Compose-based stacks — you don’t need managed Kubernetes for a blog, API, or internal tool. Providers like DigitalOcean and Hetzner both offer cheap, fast VPS instances that handle Docker Compose workloads well; we’ve used both for the deployments referenced in our best VPS providers for Docker comparison. If uptime monitoring for your containers matters to you (and it should once something’s in production), a lightweight service like BetterStack can alert you the moment a container or health check fails, before your users notice.
Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.
FAQ
Do I need Docker Compose if I already have a Dockerfile?
Only if you’re running more than one container, or you want simplified local dev commands (networking, volumes, env vars) instead of long docker run flags. A single-container app can ship with just a Dockerfile.
Can Docker Compose build images without a Dockerfile?
No — if a service in your Compose file uses build:, it needs a Dockerfile (or an inline dockerfile_inline field) to know how to build that image. Services using image: instead pull prebuilt images and skip this entirely.
Is Docker Compose the same as Docker Swarm or Kubernetes?
No. Compose is meant for single-host orchestration, typically local development or small production deployments. Swarm and Kubernetes handle multi-host clustering, scaling, and self-healing — much more complex problems that Compose doesn’t attempt to solve.
Which file should I write first, Dockerfile or docker-compose.yml?
Start with the Dockerfile for each custom service you’re building. Once each image builds and runs correctly on its own, wire them together in a Compose file with the supporting services (databases, caches, proxies) added around them.
Can I use environment variables in both files?
Yes. Dockerfiles support ARG (build-time) and ENV (runtime) instructions. Compose files can reference a .env file automatically or pass environment: values per service, which can override what’s baked into the image.
What’s the difference between docker-compose and docker compose (with a space)?
The hyphenated docker-compose is the legacy standalone Python tool. docker compose (no hyphen) is the newer Go-based plugin bundled with modern Docker Engine installs. Functionally similar, but the plugin version is faster and actively maintained — use it going forward.
Wrapping Up
A Dockerfile answers “how do I build this one image?” Docker Compose answers “how do these containers run together?” Neither replaces the other — a typical project uses a Dockerfile per custom service and one Compose file to orchestrate the whole stack. Once you’re comfortable with both, the next step is usually tightening up networking and volume persistence, which is exactly what our Docker volumes explained guide walks through.
Leave a Reply