Original post

A basic webserver

Docker containers are small OS images in themselves which one can deploy and run without worrying about dependencies or interoperability. All the dependencies are packed in the same container file. And the docker runtime takes care of the interoperability. You are not tied to using a single language or framework. You can write code in Python, , Java, Node.js, or any of your favorite languages and pack it in a container.

Consider a simple example of a Go-based webserver

We can build this using

and run it using

You can test it at http://localhost:8080

Building the Docker image

Now to package it into a docker container, we will write a Dockerfile, we will use alpine Linux as the base, since it is a small image. How small? Let’s check.

First, pull (download) the image. $ docker pull alpine Now, check its size using this long command

That’s the base Linux image; we will use. We want the base image to be small. In particular, up to 100MB is acceptable, and larger images usually cause slow start times and other problems.

Also, it is a good idea to pin to a particular version (tag) of the image. You can see all the versions at Docker Hub. We will compile our code right inside the image, which will be used for running it to avoid portability issues.

Now, write the Dockerfile.

To build it, we will use docker build command.

We will use Docker build kit since its a new fancy way of building docker images.

The command failed. Oops, we forgot to install Go runtime for building this. There are two ways to do this, either we can install go build toolchain explicitly, or we can just an image someone else has built for us. Let’s do the latter and change our Dockerfile to the following

Now build it with $ DOCKER_BUILDKIT=1 docker build -f Dockerfile -t my_hello_world_server .

Run the container

Run the container using

Check it out at http://localhost:8080/

If you kill and try to start again, you will seedocker: Error response from daemon: Conflict. The container name "/my_hello_world_server" is already in use by container "e22e524035e3d939e431c1672945f7f962daecaa1c6368bb66a8ec2e6d408cbc". You have to remove (or rename) that container to be able to reuse that name.To deal with that just delete that name with docker rm my_hello_world_server

Or even better run with

Optimizing Container Image Size

There is one problem, though; our docker container image is big. Check its size with$ docker image inspect my_hello_world_server --format='{{.Size}}' | numfmt --to=iec-i 350Mi

Wait for what? 350MB for just a hello world web server?

Our binary is small, and this indicates that something else is going on.$ du -shc bin/server 7.0M bin/server 7.0M total

Let’s check the size of the base image

So, the base image which we need for building the binary is enormous. But we don’t need to go build chain at the time of execution. There are two options. We can build it on our machine outside the docker container and copy the binary. But that’s frowned. One has to be careful to build it for the right architecture. When you build and run it inside the same container architecture, you get that portability guarantees for free.

Another alternative approach is to do what’s called a multi-stage build. We will build the binary in one docker stage and then copy only that binary over to the next step.

So, let’s write Dockerfile2

Build and verify that it works$ DOCKER_BUILDKIT=1 docker build -f Dockerfile2 -t my_hello_world_server2 . $ docker rm my_hello_world_server2; docker run --name my_hello_world_server2 -p -it my_hello_world_server

And check its size

Remember, 5.4MB was the base image, and 7MB is our new web server binary, so, this is the smallest we can get to anyways.

A build-time optimization

Right now, your build step is taking less than a second. Let’s try to what happens when we end up having a lot of unrelated files.

It takes about ~47 seconds on my machine at the “Transfer context…” stage. What happens is that docker build happens on a docker server, and everything from the directory (which we specified as “.” while building) is transferred to the Docker server to build. The server will discard extraneous files, so your final image size is still the same, but the build time becomes significant. To avoid this problem, add src/testfile to .dockerignore file.

Now, try building again, and your build times would be back to normal. It is best to exclude big dirs like .git or binfrom the Docker build step to keep the builds fast.


Docker images don’t persist anything. The idea is to run stateless machines that are completely clean and which connect to stateful storage like object-storage or SQL to store data. So, Docker cleanly abstracts out storage and execution.

Look forward to the part 2 of this post which talks about deploying Docker images to Google Cloud Run.