skip to content

With Docker Compose, you edit the API's source and run `docker compose restart api`, but the old code still runs. Why, and what fixes it?

level: middleimportance: must knowfreq 56%

answer

  1. where the code actually lives
  2. restart reuses the same container
  3. plain up skips an existing local image
  4. a build flag on up

basics

~20 s

The code is baked into the image, and docker compose restart only restarts the existing container from that old image. Rebuild and recreate with docker compose up -d --build api; a plain up skips the build because a local image already exists.

solid answer

~40 s

When the Dockerfile copies the source into the image, `docker compose restart api` stops and starts the **same container** from the **same image**, and it does not apply `compose.yaml` edits either. A plain `docker compose up -d` does not help: Compose builds a service with a `build` section only when its image is missing locally, and it recreates a container only when the service's config or image changed. `docker compose up -d --build api` forces the build, and because the image changed, Compose recreates the container. `docker compose build api` followed by `up -d` does the same in two steps, while `--force-recreate` alone recreates from the stale image. If the source were bind-mounted instead, no rebuild would be needed; the process would only have to reload.

code

bash · 5 lines
bash
# the Dockerfile COPYs ./api/src into the image
docker compose restart api            # same container, same old image
docker compose up -d api              # no build: a local image already exists
docker compose up -d --force-recreate api   # new container, still the old image
docker compose up -d --build --wait api     # rebuild, recreate, wait until running

go deeper

for a junior

Recall that the code is inside the image, so a restart replays the old image. The command to remember is docker compose up -d --build for the service you changed.

for a middle

Explain the two decisions up makes: build only when the image is missing locally, or with --build or pull_policy build, and recreate only when config or image changed. Contrast restart, --force-recreate and build.

for a senior

Show that you can debug a stale container quickly: check which image it runs with docker compose images, spot a volume masking the code directory, and choose between rebuild, bind mount or watch for the loop.

for a principal

Speak to the cost of the loop: a fast path for source edits, rebuilds reserved for dependency changes, and one documented command so every developer iterates the same way.

## Where the code actually lives When a service has a `build` section, Compose runs its Dockerfile, and a `COPY` step bakes the source into an **image**. A container is a running instance of that image with a thin writable layer on top. Editing files on the host changes neither the image nor the container, unless the source directory is **bind-mounted** into the container, in which case the running process sees edits directly and only has to reload. The question is therefore not "how do I restart it" but "how do I get a new image and a container made from it". ## What each command actually does | Command | Rebuilds the image? | Recreates the container? | New code runs? | |---|---|---|---| | `docker compose restart api` | no | no, same container | no | | `docker compose up -d api` | only if the image is missing locally | only if config or image changed | no | | `docker compose up -d --force-recreate api` | no | yes, from the old image | no | | `docker compose build api` | yes | no | not until the next `up` | | `docker compose up -d --build api` | yes | yes, because the image changed | yes | `restart` also ignores edits to `compose.yaml`: a changed environment value, for example, is not applied by a restart. `up` is the command that reconciles the file with the running containers. ## Why a plain `up` skips the build 1. Compose works out the image name: the service's `image` value, or `<project>-<service>` when `image` is absent. 2. If that image already exists locally, and neither `--build` nor `pull_policy: build` applies, Compose does not build. 3. Compose labels each container with the digest of the image it was created from and a hash of the service's configuration, and `up` recreates a container only when one of them changed. 4. Your edit changed neither, so `up` leaves the running container alone. `--build` changes step 2: for services with a `build` section, Compose applies the `build` pull policy for that invocation and builds, and the new image digest triggers the recreation in step 3. ## `build`, `image` and `pull_policy` together - **`image` names the build result.** `build: ./api` plus `image: shop-api:dev` tags the built image `shop-api:dev`. - **Both set, no `pull_policy`:** when the image is missing locally, Compose first tries to pull it and builds from source if the pull fails. - **`pull_policy: build`** builds on every `up`, even when the image already exists. - **`pull_policy: missing`** pulls only when the image is absent; it is the default for services without a `build` section. - **`always`** pulls every time, **`never`** uses only the local image and fails without one, and **`daily`**, **`weekly`** and **`every_<duration>`** check the registry again once that interval has passed since the last pull. - **`up --no-build`** never builds, even when the policy says so. ## The everyday command: `up -d --build --wait` `docker compose up -d --build --wait api` rebuilds the image, recreates the container and returns only once the service is running (or healthy, when it defines a healthcheck), exiting non-zero if it does not get there or if `--wait-timeout` seconds pass. `--wait` implies detached mode, so `-d` is redundant but harmless. What counts as healthy is configured in the compose file, not by this flag. ## Checking what is actually running When the code still looks stale, prove it rather than guess: 1. `docker compose images api` shows the image and image ID the `api` container was created from; compare it with the image you just built. 2. `docker compose ps api` shows when the container was created; a container older than your build was never recreated. 3. `docker compose exec api ls -l /app` (or reading one changed file) shows what the running filesystem really contains. 4. If the file is right in the image but wrong in the container, look for a volume mounted over that directory. ## When the rebuild loop is too slow - Order the Dockerfile so dependency installation comes before the source `COPY`, letting a code edit reuse the cached layers. - Bind-mount the source when the app reloads on file changes. - Use `docker compose watch` with `sync` rules for source and `rebuild` rules only for dependency files. - When you suspect a stale image, `docker compose images` shows which image each container runs. The one-sentence answer: **the code is in the image, so change the image (`--build`) and let `up` recreate the container from it.**

  • When would `docker compose up -d --build api` still not show your change?
    Three common causes: the edited file is outside the service's build context or excluded by `.dockerignore`, so the image never contains it; a volume is mounted over the code directory, and an anonymous volume's old content is carried into the recreated container unless you add `-V` (`--renew-anon-volumes`); or you rebuilt a different service than the one serving your requests.
  • What does `pull_policy: build` change on a service that also sets `image`?
    Compose builds the image on every `up`, even if an image with that name exists locally, and tags the result with the `image` name; it never pulls that image from a registry. Passing `--build` on the command line applies the same policy for one invocation.
  • Why does `docker compose up -d --force-recreate api` not help here?
    `--force-recreate` throws away the container and creates a new one, but from the image that is already local, which is still the old build. It is for resetting a container's state or configuration, not for picking up a code change.

saying these in an interview costs you the question

  • docker compose restart api picks up both code changes and compose.yaml edits.
  • docker compose up -d always rebuilds every service that has a build section.
  • --force-recreate rebuilds the image before it recreates the container.
  • docker compose build api on its own replaces the running api container.
  • Adding image: to a service with build: means Compose will never build it.