How to Reduce Docker Image Size

Good day. It's Monday, Aug. 26, and in this issue, we're covering:

  • How to Reduce Docker Image Size

  • GitHub Enterprise Server vulnerable to critical auth bypass flaw

  • Kubernetes 1.31 Brings More Stability to Cloud-Native Deployments

  • Docker Best Practices: Choosing Between RUN, CMD, and ENTRYPOINT

  • EKS Secret Management - with Golang, AWS ParameterStore and Terraform

  • A curated list for awesome Kubernetes sources

You share. We listen. As always, send us feedback at hello@techopsexamples.com

Use Case

How to Reduce Docker Image Size

Minimizing Docker image sizes accelerates container deployment, and for large-scale operations, this can lead to substantial savings in storage space.

1. Use Official Minimal Base Images:

When building Docker images, always start with an official base image. Instead of using a full-sized OS image, opt for lightweight versions like python:3.9-slim or python:3.9-alpine. These minimal images contain only the essentials, significantly reducing the image size.

Taking an example for a Python image, here are the image sizes for python:3.9 vs python:3.9-alpine:

Python 3.9-alpine is a whomping 95.2% smaller than Python 3.9.

2. Minimize Layers:

Every command in your Dockerfile (like RUN, COPY, etc.) generates a separate layer in the final image. Grouping similar commands together into one step decreases the total number of layers, leading to a smaller overall image size.

Instead of doing this:

RUN apk update
RUN apk add --no-cache git
RUN rm -rf /var/cache/apk/*

Do this:

RUN apk update && apk add --no-cache git && rm -rf /var/cache/apk/*

3. Use .dockerignore File:

When creating Docker images, Docker transfers all the files from your project directory into the image by default. To avoid including unneeded files, use a .dockerignore file to exclude them.

Sample .dockerignore

__pycache__
*.pyc
*.pyo
*.pyd
venv/

4. Multi-Stage Builds (Mandatory atleast for me ๐Ÿ˜€):

Multi-stage builds enable you to divide the build process from the final runtime environment. This approach is particularly beneficial when your application needs certain tools for compiling that are not necessary in the final image.

Single Stage Vs Multi-Stage Builds Comparison:

Take an example of a Flask app built using the python:3.9-alpine image with a single-stage Dockerfile like:

# Use an official Python runtime as a parent image
FROM python:3.9-alpine

# Install necessary build dependencies
RUN apk add --no-cache build-base \
    && apk add --no-cache gfortran musl-dev lapack-dev

# Set the working directory
WORKDIR /app

# Copy the requirements file and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code to the working directory
COPY . .

# Expose the port the app will run on
EXPOSE 5000

# Run the Flask app
CMD ["python", "app.py"]

Docker - Single Stage Build Output

The image built was of size: 588 MB

Redesigned Multi Stage Dockerfile looks like:

# Dockerfile.multi-stage

# Stage 1: Build
FROM python:3.9-alpine AS builder

# Install necessary build dependencies
RUN apk add --no-cache build-base \
    && apk add --no-cache gfortran musl-dev lapack-dev

# Set the working directory
WORKDIR /app

# Copy the requirements file and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code to the working directory
COPY . .

# Uninstall unnecessary dependencies
RUN pip uninstall -y pandas && apk del build-base gfortran musl-dev lapack-dev

# Stage 2: Production
FROM python:3.9-alpine

# Set the working directory
WORKDIR /app

# Copy only the necessary files from the build stage
COPY --from=builder /app /app

# Expose the port the app will run on
EXPOSE 5000

# Run the Flask app
CMD ["python", "app.py"]

Docker - Multi Stage Build Output

The new image size was: Only 47.7 MB

The application works exactly the same, but it spins up much faster in this version.

This is an illustration of the drastic effect of Multi-Stage Builds.

5. Use Static Binaries and the 'scratch' Base Image:

If your application can be compiled into a static binary, you can use the scratch base image, which is essentially an empty image. This leads to extremely small final images.

Example:

FROM scratch
COPY myapp /
CMD ["/myapp"]

Works well for applications that donโ€™t need operating system-level dependencies.

Security Considerations

  • Use Trusted and Official Base Images

  • Run Containers as Non-Root Users

  • Regularly scan your Docker images for known vulnerabilities

  • Limit the network exposure of your container by restricting the ports and IP addresses

docker run -p 127.0.0.1:8080:8080 myimage
  • Avoid hardcoding sensitive information like API keys or passwords directly into your Dockerfile or environment variables.

Final reminder,

Less the image size = Faster deployments + Quicker scaling + Lean infrastructure

p.s. if you think someone else you know may like this newsletter, share with them to join here

Tool Of The Day

IntelOwl - Get threat intelligence data about a malware, an IP address or a domain from multiple sources at the same time using a single API request

Trends & Updates

Resources & Tutorials

Picture Of The Day

Did someone forward this email to you? Sign up here

Interested in reaching smart techies?

Our newsletter puts your products and services in front of the right people - engineering leaders and senior engineers - who make important tech decisions and big purchases.