Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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 up is idempotent — run it multiple times and you get the same result (containers are simply started if they already exist). It's like terraform apply for single-host container stacks.

How it works step by step

When you type docker compose up, Compose performs these phases in order:

  1. Parse – Read docker-compose.yml (or -f overridden file) and validate syntax.
  2. Resolve – Merge environment variables from .env file and override files.
  3. Plan – Compare current state against desired state. If a container image has changed (or Dockerfile changed), mark it for rebuild.
  4. 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.
  5. Build / pull – For each service, either build the image (if build: key exists) or pull it (if image: key exists).
  6. Create containers – Docker instantiates each service as a container, assigning IP addresses inside the network.
  7. Start in dependency order – Services with depends_on: wait until the depended service is running. Compose does not wait for readiness (that requires healthchecks).
  8. Attach logs – By default, it streams combined stdout/stderr of all containers to your terminal. Press Ctrl+C to 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 Dockerfile changes, Compose automatically rebuilds the image on next up (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 -v to 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 up for 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 -d flag and then pressing Ctrl+Z to background — kills the terminal session. Always use -d if you want to detach.
  • Assuming depends_on waits for the database to be ready (healthchecks required). App containers often crash because Postgres is still initializing.
  • Running docker compose up from the wrong directory or without the correct -f flag — Compose looks for docker-compose.yml in the current directory only.
  • Not using --build after changing Dockerfile or requirements.txt — Compose uses cached images even if context changed blindly.

Variations

  1. 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+.
  2. Use docker compose up --abort-on-container-exit to stop all services if any one fails — useful in CI/CD pipelines.
  3. Use docker compose up --no-deps to start a service without its dependencies — helpful when debugging an individual container.

Real-world use cases

  • A developer runs docker compose up -d to 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-exit to spin up test infrastructure, run integration tests, and tear down automatically.
  • A production-like staging environment uses docker compose up --scale worker=5 to simulate multiple background job workers before deployment.

Key takeaways

  • docker compose up reads docker-compose.yml and creates/starts all resources defined there in correct order.
  • Use -d to run containers in background; attach logs later with docker 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 --build to up, or run docker compose build separately.
  • Stop with docker compose down (removes containers, networks, but not volumes) or docker compose down -v to wipe volumes too.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.