Build a super minimalistic Docker Image to run your Golang App
Tags: docker, cloudnative, webdev, go
Published: 2021-03-24 01:50:06

# Why should you care about Docker Images size ?

Basically, to speeds up _building_, _deploying_ and also cut costs with storage and network `egress` if you're using some cloud provider.
We can achieve minimal Docker images size using base images thats focus on minimalism such Alpine Linux and others strategies like  ***Multi Stage*** build which is the approach that I use combining with `SCRATCH ` base image.

# Practical Example

Let's play with Docker, even if you don't have it installed in your machine, use the :whale2: [online lab](https://labs.play-with-docker.com/) it's awesome and gives a real Docker experience through a web browser.

When you sign in into the online lab you can clone the demonstration project and guess what ? There's git binary ready to use in the running instance, so:
```sh
git clone -b super-minimalistic-docker-image https://github.com/iamseki/dev-to.git
```

### A brief of Multi Stage build

The main idea is divide Dockerfile into multiple stages passing to following stage just the necessary components to run the image properly. Cool, but how ? 
The oficial explanation ***FROM*** Docker webpage:
> Using multiple `FROM ` statements in your Dockerfile. Each `FROM ` instruction can use a different base, and each of them begins a new stage of the build.

### Containerizing a Golang Web App

For this example I continue my **[previous post example](https://dev.to/chseki/how-i-mock-unit-tests-in-golang-3dcp)** exposing a http server to handle a simple `GET` request.
You can take a look in the full source code in [github repo](https://github.com/iamseki/dev-to/tree/super-minimalistic-docker-image). The Dockerfile was written as follow:

```Dockerfile
FROM golang:alpine as builder

WORKDIR /app 

COPY . .

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" .

FROM scratch

WORKDIR /app

COPY --from=builder /app/dev-to /usr/bin/

ENTRYPOINT ["dev-to"]
```

Building and checking the image size:
1.  if you didn't do yet `cd dev-to`
2. `docker build -t dev-to .`
3. `docker images | grep dev-to -B 1`

Expected output:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vjfshwiwxiec2qsnapz.png)

Voila ! The generated Docker Image has ***4.55MB*** and it's a runnable web server -> `docker run --rm -p 8080:8080 -d dev-to`

Make `HTTP GET` request in the only route exposed by the server doing:

```sh
curl localhost:8080/events
```

 The response should be a JSON of fake events.

### What if we remove Multi Stage build from Dockerfile ?

Comment some lines or just rewrite the Dockerfile, for example:

```Dockerfile
FROM golang:alpine as builder

WORKDIR /app 

COPY . .

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" .

RUN mv dev-to /usr/bin/

ENTRYPOINT ["dev-to"]
```

And we got an image of size ***324MB***.

# Conclusion

This kind of approach is great for static languages like Golang but even with these languages that compiles a raw binary to be executed by the OS it's always a good idea to be careful when writing Dockerfiles to optimizing the CI/CD proccess and cut some costs.
The scratch base Image contains nothing so it's lightweight but you can't debug container from inside, to do so you can download binaries with `RUN` _command_ but I encorage you to use the `busybox` as base Image, is also lightweight but non-empty so it's possible to debug containers from inside with exec commands such as `docker exec -ti container-name sh`.