Containerizing your Go Applications with Docker
Tags: go, docker
Published: 2022-03-03 14:52:23

# Containerizing your Go Applications with Docker

Create `docker-go` folder and Build a simple Web Server in Go. For create the main.go file and add the below code

```
package main

import (
    "fmt"
    "html"
    "log"
    "net/http"
)

func main() {

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
    })

    http.HandleFunc("/hi", func(w http.ResponseWriter, r *http.Request){
        fmt.Fprintf(w, "Hi")
    })

    log.Fatal(http.ListenAndServe(":8083", nil))

}
```
if we want to run this go run main.go which will kick of a server on http://localhost:8083.

### Write Dockerfile

```
## We specify the base image we need for our
## go application
FROM golang:1.16-alpine
## We create an /app directory within our
## image that will hold our application source
## files
RUN mkdir /app
## We copy everything in the root directory
## into our /app directory
ADD . /app
## We specify that we now wish to execute 
## any further commands inside our /app
## directory
WORKDIR /app
## we run go build to compile the binary
## executable of our Go program
RUN go build -o main .
## Our start command which kicks off
## our newly created binary executable
CMD ["/app/main"]
```

Run the below command to create the docker image

docker build -t docker-go .

It will build the code and showing the below output in your terminal

```
C:\work\latest\docker-go>docker build -t docker-go . 
[+] Building 38.4s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                                                                    0.2s 
 => => transferring dockerfile: 643B                                                                                    0.0s 
 => [internal] load .dockerignore                                                                                       0.2s 
 => => transferring context: 2B                                                                                         0.0s 
 => [internal] load metadata for docker.io/library/golang:1.12.0-alpine3.9                                              3.2s 
 => [1/5] FROM docker.io/library/golang:1.12.0-alpine3.9@sha256:6c143f415448f883ed034529162b3dc1c85bb2967fdd1579a8735  29.4s 
 => => resolve docker.io/library/golang:1.12.0-alpine3.9@sha256:6c143f415448f883ed034529162b3dc1c85bb2967fdd1579a87356  0.1s 
 => => sha256:69371d496b2b4e99120216fa3c5057b0c5468411370ab24ea99cd87d7b1d9203 1.36kB / 1.36kB                          0.0s 
 => => sha256:2205a315f9c751a8c205aa42f29ad0ff29918c40d85c8ddaabac99e0cb46b5d8 3.80kB / 3.80kB                          0.0s 
 => => sha256:6c143f415448f883ed034529162b3dc1c85bb2967fdd1579a873567b22bcb790 2.37kB / 2.37kB                          0.0s 
 => => sha256:8e402f1a9c577ded051c1ef10e9fe4492890459522089959988a4852dee8ab2c 2.75MB / 2.75MB                          0.7s 
 => => sha256:de1a1e452942df2228b914d2ce9be43f18b137f4ebc3dce9491bc08c2630a2ea 154B / 154B                              0.7s 
 => => sha256:ce7779d8bfe3415e27ec3bf5950b2ab67a854c608595f0f2e49066fb5806fd12 301.88kB / 301.88kB                      0.8s 
 => => extracting sha256:8e402f1a9c577ded051c1ef10e9fe4492890459522089959988a4852dee8ab2c                               0.7s 
 => => sha256:a8c461e224a623234c9f2ff60e4249678c9e6847add7152ac80239b13d14df4c 126B / 126B                              1.0s 
 => => sha256:1bdc943bc000449a960c5121688afe0a9b51837407bf0478391b6bad6642d36f 124.28MB / 124.28MB                     15.7s 
 => => extracting sha256:ce7779d8bfe3415e27ec3bf5950b2ab67a854c608595f0f2e49066fb5806fd12                               0.5s 
 => => extracting sha256:de1a1e452942df2228b914d2ce9be43f18b137f4ebc3dce9491bc08c2630a2ea                               0.0s 
 => => extracting sha256:1bdc943bc000449a960c5121688afe0a9b51837407bf0478391b6bad6642d36f                              11.8s 
 => => extracting sha256:a8c461e224a623234c9f2ff60e4249678c9e6847add7152ac80239b13d14df4c                               0.0s 
 => [internal] load build context                                                                                       0.2s 
 => => transferring context: 1.06kB                                                                                     0.1s 
 => [2/5] RUN mkdir /app                                                                                                1.7s
 => [3/5] ADD . /app                                                                                                    0.3s 
 => [4/5] WORKDIR /app                                                                                                  0.2s 
 => [5/5] RUN go build -o main .                                                                                        2.1s 
 => exporting to image                                                                                                  0.6s 
 => => exporting layers                                                                                                 0.5s 
 => => writing image sha256:9bbcb2070c03ab1affa9f2dc62292f1cea589a60c05d4796c4490c0fa31afedb                            0.0s 
 => => naming to docker.io/library/docker-go
```

We can now verify that our image exists on our machine by typing docker images: 

```
docker images
```

which will show the list of images which contain `docker-go` docker images.

In order to run this newly created image, we can use the docker run command and pass in the ports we want to map to and the image we wish to run.

```
docker run -p 8083:8083 -it docker-go
```

- p 8083:8083 - This exposes our application which is running on port 8083 within our container on http://localhost:8083 on our local machine.
- it - This flag specifies that we want to run this image in interactive mode with a tty for this container process.
- docker-go - This is the name of the image that we want to run in a container.

if we open up http://localhost:8083 within our browser, we should see that our application is successfully responding with Hello, "/".

When i see the docker image size it roughly take 800MB in size. This is absolutely massive for smaller go application. If you want to reduce this do the following steps 

## A Simple Multi-Stage Dockerfile

In order to see why multi-stage Dockerfiles are useful, we'll be creating a simple Dockerfile that features one stage to both build and run our application, and a second Dockerfile which features both a builder stage and a production stage.

Once we've created these two distinct Dockerfiles, we should be able to compare them and hopefully see for ourselves just how multi-stage Dockerfiles are preferred over their simpler counterparts!

So, The above dockerfile, we created a really simple Docker image in which our Go application was both built and run from. 

You should hopefully notice that last column states that the size of this image is 800MBs in size. This is absolutely massive for something that builds and runs a very simple Go application.

Within this image will be all the packages and dependencies that are needed to both compile and run our Go applications. With multi-stage dockerfiles, we can actually reduce the size of these images dramatically by splitting things up into two distinct stages.

let's take a look at how we could define a real multi-stage Dockerfile that will first compile our application and subsequently run our application in a lightweight Docker alpine image.

Next, we'll create a Dockerfile in the same directory as our main.go file above. This will feature a builder stage and a production stage which will be built from two distinct base images:

```
## We'll choose the incredibly lightweight
## Go alpine image to work with
FROM golang:1.16-alpine AS builder

## We create an /app directory in which
## we'll put all of our project code
RUN mkdir /app
ADD . /app
WORKDIR /app
## We want to build our application's binary executable
RUN CGO_ENABLED=0 GOOS=linux go build -o main ./...

## the lightweight scratch image we'll
## run our application within
FROM alpine:latest AS production
## We have to copy the output from our
## builder stage to our production stage
COPY --from=builder /app .
## we can then kick off our newly compiled
## binary exectuable!!
CMD ["./main"]
```

Now that we've defined this multi-stage Dockerfile, we can proceed to build it using the standard docker build command:

```
docker build -t go-multi-stage .
```

Now, when we compare the sizes of our simple image against our multi-stage image, we should see a dramatic difference in sizes. Our previous, docker-go image was roughly 800MB in size, whereas this multi-stage image is about 1/80th the size.

If we want to try running this to verify it all works, we can do so using the following docker run command:

```
docker run -d -p 8083:8083 go-multi-stage
```
if we open up http://localhost:8083 within our browser, we should see that our application is successfully responding with Hello, "/".