Things You Should Know About Docker Image and Dockerfile Optimization

Containerization has become a major trend in software development and many companies are adopting to run their applications in a containerized environment. Docker is one of the top choices for them. Developers also like to work with docker for its application portability, faster delivery cycles, flexibility, and easy usage.

Images are everything in docker which are shared and deployed across the environments. Docker containers are running versions of those images. It’s necessary to optimize docker images to keep them smaller, lightweight, high performance, and quicker to transfer and deploy.

Docker Image optimization tricks


Use Smaller Base Images 

One should always strive for using smaller base images which can significantly reduce the size of the final image. The smaller the image is, the more you can run smaller multiple containers. There is an image such as Alpine which is only 5 MB in size that makes it a brilliant base image for both utilities and production applications. Though it also depends on your application’s needs to which smallest base images suit better.

Dockerfile with alpine base image:

FROM alpine:3.4
RUN apk update
RUN apk add vim


Reduce Number of Layers

Every step we define in Dockerfile to build an image creates an intermediary image for that specific step. These intermidiate images are sometimes also known as layers. Taking an example of the above Dockerfile - docker will take the base image then execute RUN apk update and then will add the resulting files from that step as another layer on top of the base image. The docker will repeat the same process for the subsequent steps. This means now that the ultimate docker image has one base image and 2 subsequent images on top of it.

This layering feature by docker is to speed up the docker image build process as it uses an image cache mechanism, but it will make a commit after each step. Squashing multiple steps/commands using && can create a single layer in final images and doing it smartly can help to create a smaller image.

FROM alpine:3.5

RUN apk update && \
    apk add curl && \
    apk add vim


Use .dockerignore File 

.dockerignore file is a special file that is placed in the build context directory that can reduce the size of the image significantly. This file is very similar to a .gitignore file that tells Git which files or directories to ignore in a project. 

If your Dockerfile contains an ADD or COPY command for copying directories/files, then docker build may unintentionally include source files, secret files, temporary files, or any other in the final image.

├── .dockerignore
└── Dockerfile

.dockerignore is used to define rules to exclude files and directories that we do not want to be included in our final image.

.dockerignore example:

# ignore .git, .cache, .DS_Store directories


Use Multi-Stage Builds

Smaller Docker images are fast to transfer and deploy. Docker introduced multi-stage builds with the release of version 17.05 that lets you produce small and concise images. With Docker multi-stage builds, multiple FROM statements are used in Dockerfile that leverage COPY --from to copy files from one stage to another. This means you can selectively copy artifacts from one stage to another and exclude anything that you don’t want in the final image.

Let’s see an example of a simple Go “Hello world” program:

├── .dockerignore
├── Dockerfile
└── main.go

Here is code for main.go:

package main

import "fmt"

func main() {
	fmt.Println("Hello world!")

Dockerfile with single stage code:

# Single-stage Dockerfile example
FROM golang:alpine
COPY main.go .
RUN go build -o goapp .

If we build the image using the above Dockerfile then the final image size is around 370 MB.

$ docker build -t hello:world .

Let’s now change the Dockerfile with multi-stage code. Here is a new version of the same Dockerfile:

# Multi-stage Dockerfile example
FROM golang:alpine AS build-env
COPY main.go .
RUN go build -o goapp .

# This is the final stage
FROM alpine
COPY --from=build-env /app /app/

If we rebuild the image using this Dockerfile then the final image size is just 7.64 MB.


Remove Unnecessary Packages/Dependencies

One of the best practices that keep your Docker image small is to ensure you are running the latest version of the platform (and dependencies) you are building on. By having the latest version, delete old packages from caches that can take a significant amount of space.

Let us see an example for Alpine based platform:

FROM alpine:3.5

RUN apk update && \
    apk add --virtual .build-deps --no-cache gcc python-dev py-pip linux-headers musl-dev && \
    apk del .build-deps

The --no-cache option allows docker build to not cache the index locally.

Often you may need to install some dependencies to install other required packages but you don’t need them to run your application. For this, you can use the --virtual flag to indicate such a group of packages/dependencies, and then you will be able to remove all of them (apk del).

You can add the below line to clean up cache:

apk cache clean


Use Volumes to Store Application Data

Storing application data in the images can drastically increase the size of an image. It's always good practice to use volumes to store application data in production environments.



  • Always build your docker images from smaller base images
  • Use .dockerignore file to prevent unnecessary and unintentional files/directories from being added into the docker image.
  • Combining multiple layers in Dockerfile and use of multi-stage builds can reduce image size dramatically.
  • Always remove unnecessary packages and dependencies from the image and use Volume to store application data.

Share post


Tilak S.

Technology freak, Open Source lover. Someone trying to understand many things. Wants to make a difference. Life liver and Peripatetic.

Other posts you might like