Why was my container 5x bigger than it needed to be?
March 19, 2025

For production environments, when we’re talking about docker images, the smaller the better, it’s faster to upload the image, to download it, and its safer, the smaller the image, the smaller the chances of having a security vulnerability loose in unused code.
The idea behind multi-stage building is to do your docker image build in two or more steps, those next steps can pick up the state of the image and do more work on it.
Before it, keeping the size of images down would require you to manually clean up resources from the image, it was a common practice to have one Dockerfile for development and another one to use for production. The development version contained everything needed to build your application and the production version only contained your application and the dependencies needed to run it.
To optimize our image using multi-stage building let’s start by creating a simple go file called “hello-world.go”:
Next, let’s create our Dockerfile and add:
First, we get a golang image and rename this stage, by default, the stages aren’t named, and you refer to them by their integer number, starting with 0 for the first instruction. However, you can name your stages, by adding an
to the
instruction. Later on, you can reference this stage by its name. Note that this stage is called “BUILDER”.
Now let’s add the following line:
The "RUN" instruction will execute any commands in a new layer on top of the current image and commit the results. In this case, it creates a folder called “build ”in the container.
Let’s move our hello-world.go to our container using:
The "ADD" instruction copies new files, directories or remote file URLs from and adds them to the filesystem of the image at the path.
The "WORKDIR" instruction sets the working directory for any ,
,
,
and
instructions that follow it in the
.
This run command creates our go module.
To the next step:
This line is buiding our Go binary, makes it a staticaly-linked binary, running without external dependencies, and the GOOS=linux
indicates that the binary will be compiled for linux.
To the stage 2 we go:
With multi-stage builds, you use multiple statements in your Dockerfile. Each
instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.
However, the command "From scratch" works a little bit different and it’s useful in the context of building base images (such as debian) or super minimal images (that contain only a single binary and whatever it requires, such as "hello-world"), it will not create an extra layer in your image.
Next we set our WORKDIR, If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction.
The final step:
We copy the folder hello-world (created in the previous stage using the command) using the flag “ — from=” and referencing the name we gave our stage (BUILDER) to the following path and set the entrypoint for our container.
The result is an extremely light image:

Container image final size
When the average golang images on dockerhub can reach more than 100MB!
In conclusion, utilizing Docker’s MultiStage building technique offers significant advantages in optimizing the size of your Docker images. Implementing these optimization strategies will enhance your Docker workflows and contribute to a more efficient and scalable software development process.
DISCUSSION.LOG
No comments yet. Be the first to comment!
ADD_COMMENT
© 2025 BITWISEOPS BLOG - All rights reserved