docker compose up doesn't rebuild images by default, it reuses whatever's already cached. That's usually fine, but the moment you change a Dockerfile, add a dependency, or modify a build argument, you need to tell Compose explicitly to rebuild.
There are a few different flags involved, and they do different things. This article explains each one, when to use it, and why the default behavior catches so many people off guard.
Why docker compose up doesn't rebuild
When you run docker compose up, Compose checks whether a local image already exists for each service. If one does, it uses it. It doesn't inspect whether your Dockerfile has changed since the image was built. From Compose's perspective, an image is an image.
This is a deliberate performance trade-off. Rebuilding on every up would be slow and annoying in most workflows. But it means you have to be explicit when you actually want a fresh build.
Rebuilding with --build
The most common fix is passing --build to docker compose up:
1docker compose up --build
This tells Compose to build (or rebuild) the image for every service that has a build: section in your compose.yaml before starting containers. Docker still uses its layer cache here, so unchanged layers are reused and the build is fast if only a small part of the Dockerfile changed.
Use --build as your default whenever you're iterating on a Dockerfile. It's safe to run repeatedly and only rebuilds what Docker's cache considers stale.
Bypassing the layer cache with --no-cache
Sometimes the layer cache is the problem. If you're pulling in a dependency that's released a new version, or you're debugging a build that you suspect is using a stale cached layer, pass --no-cache to docker compose build:
1docker compose build --no-cache
This forces Docker to re-execute every instruction in the Dockerfile from scratch, ignoring all cached layers. It's slower, but it guarantees a clean build.
When you want to bust the cache and start containers in one step, run both commands together:
1docker compose build --no-cache && docker compose up
Note that --no-cache is a flag on docker compose build, not on docker compose up. Passing it directly to up is not supported in Compose v2 and will produce an error on most installations.
In practice, --no-cache is something you reach for occasionally, when a RUN apt-get install or RUN pip install is pulling a package you know has been updated, or when you're chasing a build issue that only appears after clearing cache.
Rebuilding without starting containers
If you want to rebuild images without actually starting the services, use docker compose build on its own:
1docker compose build
This is useful in CI pipelines, where you might want to build and push images separately from running them. You can target a specific service instead of rebuilding everything:
1docker compose build api
After building, docker compose up will use the freshly built images.
Forcing containers to recreate with --force-recreate
--force-recreate is a different lever entirely. It doesn't affect image building, it tells Compose to stop and recreate containers even if their configuration hasn't changed.
1docker compose up --force-recreate
The typical use case is when you've changed something that Compose wouldn't normally detect as a reason to recreate a container: a volume mount, an environment variable injected from outside the compose file, or a change in an external dependency. Compose compares the current container's configuration against the desired state and, if they match, leaves the container running. --force-recreate skips that check.
It's also useful if a container ends up in a weird state and you want a clean restart without tearing everything down manually.
Putting it together
Here's when to reach for each option:
- You changed a
Dockerfileor build context:docker compose up --build - A dependency inside the image has an update and Docker is caching the old version:
docker compose build --no-cache - You want to build images in CI without starting services:
docker compose build - The image is fine but you want fresh containers regardless:
docker compose up --force-recreate - You want a fully clean rebuild and restart:
docker compose build --no-cache && docker compose up --force-recreate
Common pitfalls
Editing a file that's copied, not mounted
A frequent source of confusion: you edit a source file, run docker compose up, and see no changes. The build ran, but the file looks the same inside the container.
Check whether the service mounts the file as a volume or copies it with COPY in the Dockerfile. If it's a COPY, the file only updates when you rebuild the image. If it's a volume mount, changes reflect immediately without any rebuild.
Running docker compose up --build will fix the COPY case. If you're using volume mounts for development, you don't need to rebuild at all, but you may need to restart the process inside the container depending on how it handles file changes.
Changing a build argument or .env variable
Build arguments (ARG in your Dockerfile, passed via build.args in compose.yaml) and variables sourced from a .env file are baked into the image at build time. Changing them won't take effect until you rebuild. If you update a build.args value or swap out a .env variable that feeds into a RUN step, run docker compose up --build to pick up the change. If Docker's cache is too aggressive and still serves a stale layer, follow it up with docker compose build --no-cache.
For guidance on accessing and centralising the logs those containers produce, see Docker Compose Logs: A Complete Guide.
Final thoughts
--build is what you need most of the time. Reach for --no-cache when a cached layer is pulling a stale dependency, but pass it to docker compose build, not to docker compose up. Use docker compose build to build without starting services. Use --force-recreate when the container itself needs a fresh start, regardless of the image.
Instrument your containers so you can correlate deployments with performance changes. Dash0 is an OTel-native platform that correlates logs, metrics, and traces across your containers, so when a rebuilt image changes behavior, you can trace it back to the exact deployment.
Start a free trial to get full-stack visibility into your containerized services.