DEV Community

murugan
murugan

Posted on

Containerizing your Go Applications with Docker

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))

}
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

docker images
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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"]
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (5)

Collapse
 
achimgrolimund profile image
Achim Grolimund

Cool small post for beginners but please use an up to date Go version.

And the Dockerfile is absolutely not best practice. Use multistage Dockerfile. Build the app in your alpine image then copy it to an scratch image. The result is an Dockerimage with an size about 6-12Mb and not 100Mb+ 😉

See here an Post: grolimund-solutions.ch/dockerimage...

It is on german but the Code is the Same 😋

Best regards

Collapse
 
aurelievache profile image
Aurélie Vache

I'm agree with you @achimgrolimund, I commented too.
I created a serie about create apps in go ok order to learning go by examples on dev.to and I also create videos series about docker and Kubernetes if it's interest you.

Collapse
 
krpmuruga profile image
murugan

Thank you for your comments.. I'll updated the documents slightly.

Collapse
 
achimgrolimund profile image
Achim Grolimund

btw: here the link to my GitRepo with this example code

github.com/GrolimundSolutions/go-m...

Collapse
 
aurelievache profile image
Aurélie Vache

It's working for an hello world but it's not the real life.
What about if your apps have go modules? Your dockerfile will be KO because of dependencies.

There are many ways to handle this case like multi stage dockerfile if you create it manually. You can also use buildpacks, goreleaser ....

So many ways 🙂