Docker Compose Up: What It Does + Common Flags + Troubleshooting

docker compose up does five things in a specific order. Most debugging becomes trivial once you know which step failed: image resolution, network/volume creation, container creation, or the actual app process.

Related: Docker Compose Restart Policy, Docker Compose Stop, Podman vs Docker, Docker manifest unknown runbook.

Quick answer: docker compose up creates (or updates) the resources from your compose.yml and starts your services. By default it won’t always pull newer images or rebuild your Dockerfile changes. Use --pull and --build when you need to force that.

Need to stop and remove containers after youre done? Read: Docker Compose Down. And if youre considering -v / --volumes, use this safety guide first: Docker Compose Down -v / –volumes (what it deletes).

Compose V2 vs V1 (quick sanity check)

This post assumes Compose V2: docker compose ... (plugin). If you use docker-compose (V1, deprecated), some flags and behaviors differ. The mental model is the same.

What exactly happens when you run docker compose up?

Here’s the real order of operations. If you’re surprised by restarts, stale code, or missing env vars, one of these steps is where the mismatch happens.

  • Loads configuration: compose.yml, overrides, .env, profiles.
  • Resolves images: decides whether it can reuse a local image, pull, or build.
  • Creates/updates infrastructure: networks and volumes referenced by services.
  • Creates/recreates containers: based on config changes (ports, env, mounts, image digest, etc.).
  • Starts containers: then shows logs (unless -d).

Image resolution: when does it pull vs build?

This is where most ‘why didn’t my change apply?’ questions live. Default behavior is conservative: if an image is already present locally, Compose typically won’t pull a newer one unless you tell it to.

  • If your service uses image: only, up will use the local copy if it exists. Use --pull=always (or --pull) to force a pull.
  • If your service uses build:, Compose can build the image. Use --build to force a rebuild when you changed Dockerfile/app code.
  • If you have both build: and image:, the image: value becomes the tag for the built image. In practice: building wins, pulling doesn’t make sense for that service.

A minimal compose.yml we’ll use in examples

Use one concrete file while reading. It makes the flags and failure modes much easier to reason about.

services:
  web:
    build: .
    image: myapp-web:dev
    ports:
      - "8080:8080"
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10
Code language: YAML (yaml)

The flags you’ll actually use (and what they change)

Some flags change behavior (pull/build/recreate), not just output. Here are the ones that matter in practice.

FlagWhat it changesWhen to use it
-dDetaches and stops streaming logsWhen you want the stack in the background
--buildForces rebuild for build: servicesAfter Dockerfile/app changes
--pull=alwaysForces pulling newer images for image: servicesWhen you expect a new tag/digest in registry
--force-recreateRecreates containers even if config seems unchangedWhen you suspect sticky container state
--no-recreatePrevents recreating existing containersWhen you want to start what exists, as-is
--remove-orphansRemoves containers not defined in current fileAfter renaming/removing a service

Bridge note: --build and --pull solve different problems. If your service is built locally, pulling won’t help. If your service is pulled from a registry, rebuilding won’t help.

Running only one service: docker compose up SERVICE

You don’t always want the whole stack. You can start a single service by name. Just remember: dependencies may still start depending on your Compose file and version.

docker compose up webCode language: Bash (bash)

Common failures and quick fixes

This section is intentionally runbook-style: fix the symptom first, then learn the ‘why’ so it doesn’t bite you again.

Port is already allocated

This happens when your host port is already in use (or a previous container still owns it). Find the process, then either stop it or change the mapping.

lsof -i :8080
# or on Linux
ss -tlnp | grep 8080Code language: Bash (bash)

Containers keep restarting / service exits immediately

A restart loop is usually just your app process exiting quickly. Compose is doing its job: keeping the service running. Your job is to inspect the real error.

docker compose logs web --tail=200
docker compose psCode language: Bash (bash)

depends_on doesn’t guarantee readiness

depends_on controls start order, not whether the dependency is actually ready. If your app boots faster than Postgres, you’ll see connection errors and restarts. Use healthchecks (as in the example file) or add retry logic in the app.

Compose can’t find environment variables

Common causes: wrong working directory, missing .env, or expecting shell exports to be present in non-interactive shells/CI. If you want predictable behavior, prefer env_file: or explicitly pass --env-file (where supported).

.env vs env_file vs environment (why variables look “blank”)

Compose has three different mechanisms that people mix up:

  • .env: used by Compose for variable substitution in the YAML (e.g. ${TAG}).
  • env_file:: loads variables into the container runtime environment.
  • environment:: sets/overrides container environment variables.

If you see warnings like “variable is not set and is defaulting to a blank string”, it usually means Compose couldn’t find the variable for YAML substitution (often because .env isn’t in the working directory you think it is).

Pull access denied / image not found

If you pull from a private registry, authenticate first. Also check that the image tag exists. The error is almost never ‘Compose is broken’.

What docker compose up does not do

  • It does not automatically pull newer images if a local image already exists (unless you use --pull).
  • It does not automatically rebuild your image for build: services (unless you use --build).
  • It does not clean up old images/volumes. That’s a separate cleanup workflow.

Orphan containers: what they are and how to clean up safely

Compose identifies a project by directory + project name. If you rename services, change directories, or run with different project names, you can end up with “orphan” containers (leftovers from an older config).

Safe sequence: start with docker compose ps -a to see what belongs to the current project. Only then consider docker compose up --remove-orphans. If you’re unsure about scope, explicitly set the project name with -p.

If youre skimming: this section is the one youll actually run.

FAQ

Does `docker compose up` rebuild images automatically?

Not reliably. If a service uses `build:`, use `docker compose up –build` after code/Dockerfile changes. If it uses `image:` only, rebuilding won’t help — you need `–pull` (and usually a new tag/digest).

What’s the difference between `docker compose up` and `docker compose start`?

`start` only starts existing containers. `up` creates and updates resources (networks/containers) when needed, so it’s the command you want after changing `compose.yml`.

When do I need `–force-recreate`?

When you suspect the container config is out of sync or you want a clean container process without changing the file. It recreates containers, but doesn’t delete named volumes.

Why does `docker compose up -d` show no logs?

Because detached mode stops log streaming. Use `docker compose logs -f` to follow logs afterwards.

How do I stop everything cleanly: `stop` vs `down`?

`stop` stops containers but keeps them around. `down` removes containers and default networks. Add `-v` only if you also want to remove volumes (data loss risk).

Related reading (internal links)

Sergio Bremming Avatar

Leave a Reply

Your email address will not be published. Required fields are marked *