docker compose up Basics
Master 'docker compose up' to launch multi-service apps with one command. This lesson covers syntax, detach mode, rebuild, and troubleshooting.
Focus: start services with docker compose up
You've built a beautiful docker-compose.yml file describing a multi-container application — a web server, a database, a caching layer. Now comes the moment of truth. Running docker compose up is the single command that brings all those containers to life simultaneously, linking them with networks and volumes as you defined. Without it, you'd be juggling docker run commands with dozens of flags and praying the networking works. This lesson makes that magic reproducible, observable, and controllable.
The problem this lesson solves
When an application grows beyond a single container — think a Python Flask API talking to PostgreSQL and Redis — starting everything manually is a nightmare. You need to:
- Create custom networks so containers can find each other by hostname
- Mount volumes for data persistence and live code reloading
- Manage environment variables across containers
- Set the correct startup order (e.g., database before app)
Doing this with raw docker run commands is error-prone, unshareable with teammates, and impossible to version-control with confidence. Docker Compose packages all that complexity into a single declarative file. And docker compose up is the key that executes that declaration.
Core concept / mental model
Think of docker compose up as the orchestra conductor. Your docker-compose.yml is the musical score — it tells the conductor what instruments (containers) to play, which ones start first (dependencies), and how they harmonize (shared network, volumes).
- Defined state – Compose reads the YAML file and builds a desired state: "2 containers, a network, a volume."
- Reconciliation – It checks what Docker objects already exist, creates missing ones, and updates or recreates those that differ.
- Execution – Finally, it runs each container with the correct entrypoint, ports, and environment.
A pro tip:
docker compose upis idempotent — run it multiple times and you get the same result (containers are simply started if they already exist). It's liketerraform applyfor single-host container stacks.
How it works step by step
When you type docker compose up, Compose performs these phases in order:
- Parse – Read
docker-compose.yml(or-foverridden file) and validate syntax. - Resolve – Merge environment variables from
.envfile and override files. - Plan – Compare current state against desired state. If a container image has changed (or
Dockerfilechanged), mark it for rebuild. - Create resources –
- Networks – Create the overlay or bridge networks defined under
networks:. - Volumes – Ensure named volumes exist. - Secrets/Configs – Prepare any Docker secrets or configs. - Build / pull – For each service, either build the image (if
build:key exists) or pull it (ifimage:key exists). - Create containers – Docker instantiates each service as a container, assigning IP addresses inside the network.
- Start in dependency order – Services with
depends_on:wait until the depended service is running. Compose does not wait for readiness (that requires healthchecks). - Attach logs – By default, it streams combined stdout/stderr of all containers to your terminal. Press
Ctrl+Cto stop gracefully.
Detach mode
If you want the stack to run in the background (like a daemon), use the -d flag:
docker compose up -d
Now your terminal is free, and the containers keep running. To see their logs later:
docker compose logs -f
Rebuild vs. reuse
- If a
Dockerfilechanges, Compose automatically rebuilds the image on nextup(unless you explicitly skip with--no-build). - For source code mounted as volumes (like Python scripts), you need to restart the container after changes:
docker compose restart.
Hands-on walkthrough
Let's build a real multi-service application step by step.
1. Project structure
Create a directory and a docker-compose.yml:
# docker-compose.yml
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
volumes:
- .:/code
environment:
- DATABASE_URL=postgresql://app:secret@db:5432/app
depends_on:
- db
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
2. A simple Python web server
Create Dockerfile (in the same directory):
# Dockerfile
FROM python:3.11-slim
WORKDIR /code
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Create requirements.txt:
uvicorn
fastapi
asyncpg
Create app.py:
# app.py
from fastapi import FastAPI
import asyncpg
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello from Compose!"}
@app.get("/db-check")
async def db_check():
conn = await asyncpg.connect(
user="app", password="secret",
database="app", host="db", port=5432
)
await conn.close()
return {"db": "connected"}
3. Start everything
docker compose up -d
Expected output snippet:
[+] Running 3/3
⠿ Network myapp_default Created
⠿ Volume myapp_pgdata Created
⠿ Container myapp_db_1 Started
⠿ Container myapp_web_1 Started
Now visit http://localhost:8000/db-check and you'll see {"db":"connected"}.
4. See the magic of hot reload
Because we mounted . into /code inside web, any change to app.py is visible inside the container immediately. But FastAPI won't reload unless we use --reload. Let's add it to the Dockerfile CMD:
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Then rebuild and start:
docker compose up -d --build
Edit app.py, save — the server restarts automatically. That's instant feedback for development.
5. Stop and clean up
docker compose stop– stops containers without removing them.docker compose down– stops and removes containers, networks (volumes persist by default). Add-vto nuke volumes too.
Compare options / when to choose what
| Command | Behavior | Use case |
|---|---|---|
docker compose up |
Build/pull, create, start, attach logs foreground | Development – want to see logs live |
docker compose up -d |
Same but daemonized | Production or background services |
docker compose up --build |
Force rebuild images even if cache valid | After changing Dockerfile or deps |
docker compose up --no-start |
Only pull/build and create resources (no start) | Pre-stage for manual start |
docker compose run <svc> |
Run a one-off command in a new container (respects ports volumes, net) | Migration, shell, debugging |
docker compose up --scale web=3 |
Create multiple replicas of one service | Load testing, limited HA |
When to choose up vs run:
- Use
docker compose upfor the full application lifecycle. - Use
docker compose run <service>for ad-hoc tasks like running a migration script:docker compose run web alembic upgrade head.
Troubleshooting & edge cases
Container crashing on startup
Error: Compose shows "exited with code 1" repeatedly.
Solution: Run docker compose logs <service> to see the actual error. Common causes:
- Missing environment variables
- Port conflict on the host (change host port)
- Database not ready yet (add restart: on-failure or a healthcheck)
Port already in use
Error response from daemon: driver failed programming external connectivity
on endpoint myapp_web_1: Bind for 0.0.0.0:8000 failed: port is already allocated
Fix: Either stop the other process or change the host port in your compose file:
ports:
- "8001:8000"
Image not rebuilt after Dockerfile change
Compose should auto-detect changes, but if you're using image: (prebuilt) instead of build:, it won't rebuild. Fix by switching to build: or running docker compose build explicitly.
Container can't reach database
Check: Are both services on the same network? Compose creates a default network for the stack. If your database service is named db, then inside the web container you can reach it as db:5432. If you used an alias in depends_on, that doesn't set hostname — networking still works by service name.
Windows / macOS file permission issues with mounted volumes
Mounting code from host may cause file permission mismatch if the container runs as a different user. Mitigate with:
user: "${UID:-1000}:${GID:-1000}"
And pass UID and GID from your .env file.
What you learned & what's next
You now know that docker compose up is the fundamental command to start services with docker compose up — it reads a YAML plan, creates all required Docker objects, orders container startup, and either attaches logs or runs in the background (-d). You can force rebuilds, scale services, and run one-off commands with run. You understand the difference between up foreground mode and up -d daemon mode, and you can troubleshoot common pitfalls like port clashes and container crashes.
Next lesson: You'll learn how to define and manage environment variables across environments — separating dev, staging, and prod configuration without duplicating your compose file. Mastering env_file, .env, and variable substitution.
Practice recap
Create a new project directory. Write a docker-compose.yml that runs two services: a Redis cache and a Python script that writes a timestamp every second to Redis. Use docker compose up and verify with docker compose logs. Then stop with Ctrl+C and clean up with docker compose down. Make a modification — change the Python script to read from Redis instead — and hot-reload by mounting a volume.
Common mistakes
- Forgetting the
-dflag and then pressingCtrl+Zto background — kills the terminal session. Always use-dif you want to detach. - Assuming
depends_onwaits for the database to be ready (healthchecks required). App containers often crash because Postgres is still initializing. - Running
docker compose upfrom the wrong directory or without the correct-fflag — Compose looks fordocker-compose.ymlin the current directory only. - Not using
--buildafter changingDockerfileorrequirements.txt— Compose uses cached images even if context changed blindly.
Variations
- Use
docker-compose up(hyphen) for older Docker Compose v1;docker compose up(space) is the modern plugin included in Docker Desktop/Engine 20.10+. - Use
docker compose up --abort-on-container-exitto stop all services if any one fails — useful in CI/CD pipelines. - Use
docker compose up --no-depsto start a service without its dependencies — helpful when debugging an individual container.
Real-world use cases
- A developer runs
docker compose up -dto start a full-stack web app (React frontend, Node API, MongoDB) on their local machine with one command. - A CI pipeline uses
docker compose up --build --abort-on-container-exitto spin up test infrastructure, run integration tests, and tear down automatically. - A production-like staging environment uses
docker compose up --scale worker=5to simulate multiple background job workers before deployment.
Key takeaways
docker compose upreadsdocker-compose.ymland creates/starts all resources defined there in correct order.- Use
-dto run containers in background; attach logs later withdocker compose logs -f. - Dependency ordering (
depends_on) only ensures containers start in sequence, not that services are ready — add healthchecks or retry logic. - Force a rebuild of images by adding
--buildtoup, or rundocker compose buildseparately. - Stop with
docker compose down(removes containers, networks, but not volumes) ordocker compose down -vto wipe volumes too.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.