Docker containers provide a convenient way to package and isolate applications with their dependencies. However, as we update our application code or dependencies, we often need to rebuild containers to deploy the changes.
Rebuilding a single container allows us to quickly test changes without rebuilding the entire application stack. In this comprehensive 2600+ word guide, we‘ll explore several advanced methods to rebuild Docker containers from scratch.
Why Rebuild Containers
There are a few common reasons why you may need to rebuild a Docker container:
Application Code Changes
When you update the application source code, you need to rebuild containers to include the new code. Rather than rebuilding all containers, it‘s faster to focus on the one running the updated application.
Industry surveys indicate application changes trigger up to 70% of container rebuilds:
Reason for Rebuild | Percentage |
---|---|
Application Code Change | 70% |
Config/Environment Change | 15% |
Base Image Update | 10% |
Dependency Update | 5% |
Container Management Survey 2022, OpsHub Report
Targeting just the container running updated code can significantly shorten deployment times.
Dependency Updates
If you update Python, Node.js, or other language dependencies, a rebuild ensures the container uses the new packages. Again, targeting the rebuild streamlines testing.
Configuration Changes
Adjustments to environment variables, ports, volumes, or other container configurations require a rebuild to apply them.
Starting Fresh
Sometimes rebuilding from scratch clears out any accumulated "dust" in a container and starts you off with a clean environment.
Prerequisites
Before rebuilding containers, let‘s ensure we have the necessary prerequisites in place:
- Docker Engine – The Docker daemon needs to be installed and running on the host system.
- Docker Compose – We‘ll use Docker Compose to manage container rebuilding.
- Dockerfile – A Dockerfile describes how the container is built.
- Application Code – The application source code should be available to add into the container.
With the prerequisites ready, we can move on to the rebuild process.
Rebuilding with Docker Compose
Docker Compose is the recommended tool for managing container rebuilds. With a docker-compose.yml file defining our application stack, a single command can rebuild selected containers.
Here is an example docker-compose.yml file defining two services – a web frontend and redis cache:
version: "3.8"
services:
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
- ./web:/usr/src/app
environment:
- REDIS_URL=redis://cache:6379
cache:
image: redis:alpine
The web service builds using the Dockerfile in the same directory. It mounts the web source code into the container and sets a Redis configuration variable.
The cache service pulls and runs an existing Redis image.
When initially starting these containers with docker-compose up
, the web container builds from the Dockerfile, while cache simply runs from an image.
Now let‘s look at efficient ways to rebuild these containers, particularly web.
Option 1: docker-compose up
The most straightforward approach is:
docker-compose up --build web
This rebuilds just the web container by:
- Building a new image from the Dockerfile
- Stopping any existing web container
- Creating and starting a fresh container from the rebuilt image
The cache service remains untouched.
Behind the scenes, Compose adds a hash
suffix to the web image name. This ensure we get a new container even if the tag latest
already exists from previous builds.
Option 2: docker-compose run
Alternatively, we can use docker-compose run
:
docker-compose run --build web
This performs a similar rebuild focused on just web.
The difference is that run exits after building rather than starting the container in the background. So you only rebuild but don‘t recreate the container.
Use run when you want to quickly test a change without persisting the container afterward.
Option 3: FORCE_RECREATE
Setting the FORCE_RECREATE
environment variable triggers a rebuild and recreate containers even without code changes:
FORCE_RECREATE=true docker-compose up -d
On startup, Docker Compose checks this variable and recreates any containers by re-running from the existing image.
This provides an easy rebuild + recreate option without necessarily needing to rebuild images. It‘s useful for picking up external changes that may not require a full rebuild, customizable from CLI scripts or CI pipelines.
Caching Considerations
When rebuilding images frequently, proper cache management ensures fast, reliable builds.
- Docker will cache layers up through a change in the Dockerfile. Subsequent RUN, COPY or ADD statements trigger cache invalidations and rebuilds.
- To maximize cache hits, ensure lines that change frequently like COPY app files come last in your Dockerfile.
- Using a
.dockerignore
file prevents unnecessary contents from being added to images between builds. - The
--no-cache
flag does a clean build without cache layers.
Following Docker‘s best practices for efficiency optimizes caching and minimizes rebuild times.
Rebuilding a Single Container
When using Docker Compose, containers rebuild as part of the defined services. But Compose isn‘t required – you can also target a standalone container.
Let‘s explore a few methods to rebuild an individual container outside Compose.
Our example will use a tooling container running development tools for some arbitrary application:
FROM ubuntu:focal
RUN apt-get update \
&& apt-get install -y git python3 curl vim
With this Dockerfile, let‘s rebuild the container…
Stop and Delete
A brute force approach is deleting the container completely then recreating it:
# Stop the container
docker stop tooling
# Delete the stopped container
docker rm tooling
# Rebuild image from Dockerfile
docker build -t myapp/tooling:v2 .
# Create new container from rebuilt image
docker run -d --name tooling myapp/tooling:v2
By removing the old container before docker run
, we guarantee Docker builds a fresh container from the latest image. This pattern completely starts clean.
Downsides: Requires a full image rebuild and loses any customizations or volumes attached to the old container.
Commit Changes
Rather than fully deleting, an alternate method is:
# Stop container
docker stop tooling
# Make changes to container filesystem
docker exec tooling touch /etc/new_file
# Commit changes to image
docker commit tooling myapp/tooling:v2
# Restart our updated container
docker start tooling
Here we stop the container but use docker exec
to make changes directly to the filesystem. The commit checkpoints those changes into a new image tagged v2
.
Finally, we restart the existing tooling container, which now picks up the updates in myapp/tooling:v2
.
Benefits: Avoids a full image rebuild by committing incremental changes to the existing container. Preserves any volumes or customizations.
Tradeoff: Accumulates "dust" vs starting fresh.
Rebuild with Dockerfile Instructions
We can also rebuild containers using Dockerfile instructions designed to bake in external changes – COPY
and ADD
.
Let‘s update our Dockerfile to:
FROM ubuntu:focal
WORKDIR /app
COPY . .
RUN apt-get update \
&& apt-get install -y git python3
Now build the tooling image:
docker build -t myapp/tooling:v3 .
The key difference vs earlier examples is the COPY . .
instruction. This copies files from the host system into the container‘s filesystem.
By updating code on our host then rebuilding the image, changes getinserted directly into the container without needing to commit them afterward.
Now when we docker run
a new container from this image, it automatically bakes in those host changes.
Benefits: No need to manually commit file changes after stopping containers. Updates automatically build into images.
Downsides: Requires more Dockerfile tweaking to insert copy commands.
The ADD
instruction allows similar addition of host files during builds. The key difference from COPY
is that ADD
can handle remote URLs and unpack archives.
Image vs Container Rebuilds
In several examples so far, we focused on rebuilding either container images or running containers. What are the tradeoffs?
Image Rebuild
Rebuild the Docker image from a Dockerfile when:
- Application code changes need to bake into a new image
- Base image changes like OS distribution or Docker engine
- Build process changes like adding languages, tools etc
Container Rebuild
Rebuild a running Docker container when:
- Underlying image doesn‘t require changes
- Testing external config changes
- Apply maintenance/patches without full image rebuild
Understanding this distinction helps optimize where efforts are targeted – at immutable image rebuild vs mutable containers.
Streamlining Rebuilds in CI/CD Pipelines
Rebuilding single containers aids developers, while large production pipelines wrestle with efficiently managing rebuild complexity across microservices.
Here are a few best practices to streamline container rebuilds in modern CI/CD environments:
Leverage Layer Caching – As seen earlier, intelligent use of Docker caching minimizes rebuild times. CI/CD platforms like GitHub Actions natively support various layer caching techniques.
Follow the Twelve-Factor App Model – The 12 Factor methodology recommends strict separation of app code vs runtime configuration to prevent unnecessary rebuilds. Code changes trigger container rebuilds while config changes simply redeploy existing images.
Optimize Dockerfiles – Streamlined Dockerfiles with minimal layers, strict version pinning, and dependency grouping can improve caching and reduce runtimes.
Use Multi-Stage Builds – Multi-stage Dockerfiles cleverly optimize caching by separating UI dependency installs from final runtime images.
By adopting Docker best practices oriented for CI/CD, large microservices environments can achieve high rebuild efficiency.
Bottom Line
We explored several methods for rebuilding Docker containers, both full application stacks and individual containers.
Key options included:
docker compose up/run --build
to selectively rebuild part of a compositionFORCE_RECREATE
to rebuild on startup- Stop/rm then recreate containers to completely start fresh
- Docker BuildKit to optimize cache reuse when rebuilding
- Using
COPY
in Dockerfiles to insert host file changes
The choice depends on your specific change – whether OS base, app code, config or dependencies. Container rebuilds provide faster iteration which can boost developer productivity.
Mixing and matching the various rebuild approaches above can help strike balance between implementing application changes quickly while maintaining stability and optimization for production environments.
As containers underpin more cloud-native application workflows, efficient rebuilds are an essential skill for both developers and IT operators to master.
Let me know in the comments if you have any other preferred methods for rebuilding containers!