When building Docker images, choosing the right build strategy can significantly affect the size, security, and performance of your containers. The three common strategies are Normal Build, Multi-Stage Build, and Distroless Build. Each has its strengths and trade-offs depending on your needs. Let’s dive into when and why you might choose each one.
1. Normal Build
In a normal Docker build, we start by using a base image, add our application code, and install dependencies. This results in a container that contains both your application and the full operating system or environment that comes with the base image.
Example Python Script (hello.py):
print("Hello World")
Dockerfile (Normal Build):
# Use the official Python image
FROM python:3.9
# Set the working directory inside the container
WORKDIR /app
# Copy the Python script into the container
COPY hello.py .
# Run the Python script
CMD ["python", "hello.py"]
In this case, the container will include Python and the full Python runtime environment. This results in a larger image since it includes both the operating system and Python libraries.
Build and Run:
docker build -t python-hello-world .
docker run python-hello-world
2. Multi-Stage Build
A multi-stage build strategy allows you to use one image to build the application and another, lighter image to run it. This helps reduce the final image size by only copying over the necessary artifacts, such as compiled code or installed dependencies.
Example Python Script (hello.py):
print("Hello World")
Dockerfile (Multi-Stage Build):
# Stage 1: Build environment (with full dependencies)
FROM python:3.9 AS build-env
# Set the working directory inside the container
WORKDIR /app
# Copy the Python script into the container
COPY hello.py .
# Install any dependencies (optional)
RUN pip install --no-cache-dir requests
# Stage 2: Runtime environment (lighter base image)
FROM python:3.9-slim
# Set the working directory inside the container
WORKDIR /app
# Copy only the necessary files from the build-env stage
COPY --from=build-env /app /app
# Run the Python script
CMD ["python", "hello.py"]
How It Works:
In Stage 1, the python:3.9 image is used to build the app, install dependencies, and prepare everything.
In Stage 2, the dependencies and app files are copied into a much smaller image, python:3.9-slim, which has a minimal footprint and only includes the essentials.
Build and Run:
docker build -t python-hello-world-multi .
docker run python-hello-world-multi
By separating the build and runtime environments, you can drastically reduce the size of the final image.
3. Distroless Build
A Distroless Build takes minimalism to the extreme. It only includes the application and the necessary runtime libraries, leaving out everything else — including the operating system, shell, package manager, or debugging tools. Distroless images focus on security and performance, but come with trade-offs: debugging and troubleshooting become more difficult due to the absence of shell access.
Example Python Script (hello.py):
print("Hello World")
Dockerfile (Distroless Build):
# Stage 1: Build environment
FROM python:3.9 AS build-env
# Set the working directory inside the container
WORKDIR /app
# Copy the Python script into the container
COPY hello.py .
# Install any dependencies (optional)
RUN pip install --no-cache-dir requests
# Stage 2: Final runtime image (distroless)
FROM gcr.io/distroless/python3
# Set the working directory inside the container
WORKDIR /app
# Copy only the necessary files from the build-env stage
COPY --from=build-env /app /app
# Run the Python script
CMD ["python", "hello.py"]
How It Works:
In Stage 1, the full python:3.9 image is used to build the app and install any dependencies.
In Stage 2, the gcr.io/distroless/python3 image is used, which only contains Python and essential runtime libraries. No shell, no debugging tools, just the application and its dependencies.
Build and Run:
docker build -t python-hello-world-distroless .
docker run python-hello-world-distroless
Because the distroless image is minimal, it is both smaller and more secure. However, troubleshooting becomes much harder without tools like bash or curl.
Conclusion
The build strategy you choose for your Docker image depends on the nature of your application and your specific needs. If you’re working on a simple app and need a straightforward build, a Normal Build might be the best choice. For more complex applications with dependencies or compilation steps, a Multi-Stage Build helps reduce the final image size while still providing flexibility during the build process. Finally, for production environments focused on security and efficiency, a Distroless Build offers a minimal, secure, and optimized solution, though at the cost of some debugging convenience.
Top comments (1)
Hope this article helps you!!