Dockerize-Go-Application-Easily

When you become a parent, one thing becomes really clear.
And that's that you want to make sure your children feel safe.

There are some ways to deploy your Golang code, especially when you are using Docker to run your executable file of your Go Project. We can create our image from our project, and we can simply run it on your local computer, or even on the deployment by pulling your image from the registry.

Requirement

Getting Started

First, you need to start your docker daemon by using systemctl start docker or service docker start , use sudo if needed.

Then we will create our simple go HTTP code.

Then we will create our simple go HTTP code.

1
2
3
$ mkdir go-dockerfile && cd go-dockerfile
$ go mod init myapp
$ touch server.go

server.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"os"

"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
)

func init() {
godotenv.Load()
}

func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

router := gin.Default()

router.GET("/", func(c *gin.Context) {
c.String(200, "Hello World")
})

router.GET("/env", func(c *gin.Context) {
c.String(200, "Hello %s", os.Getenv("NAME"))
})

router.Run(":" + port)
}

Our server.go will contain a simple gin router and optional godotenv .

/ path will return “Hello World” and /env path will return “Hello ${NAME}”.

Dockerfile

there are several ways to write Dockerfile , but I will make 3 examples with different base images: official golang, alpine, and scratch.

FROM Official Image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM golang:1.16-alpine

WORKDIR /project/go-docker/

# COPY go.mod, go.sum and download the dependencies
COPY go.* ./
RUN go mod download

# COPY All things inside the project and build
COPY . .
RUN go build -o /project/go-docker/build/myapp .

EXPOSE 8080
ENTRYPOINT [ "/project/go-docker/build/myapp" ]

In this Dockerfile , we will split it into some sections:

  • FROM golang:1.16-alpine , we will use golang:1.16-alpine as the base image of this Docker build.
  • WORKDIR , will be our working directory of our command/path of our next commands.
  • COPY go.* ./ , we will copy go.mod & go.sum file from our project to the working directory.
  • RUN go mod download , download the project dependencies from go modules.
  • COPY . . , copy all things from our project into the working directory.
  • RUN go build -o /project/go-docker/build/myapp . , build our project in the working directory and output it in project/go-docker/build/myapp as a binary file.
  • EXPOSE 8080 , telling docker that our code will expose port 8080 .
  • ENTRYPOINT ["/project/go-docker/build/myapp"] , when we run the container of this image, it will start from our build binary.

Any of these duplicate explanations won’t be explained twice. After this we need to run this command:

1
docker build -f Dockerfile -t test-go-docker:latest .
  • -f flag is the filename of our Dockerfile .

  • -t flag is the name of the image later on.

  • . at the end of the command is the directory of the Dockerfile .

Alpine Base Image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM golang:1.16-alpine as builder

WORKDIR /project/go-docker/

# COPY go.mod, go.sum and download the dependencies
COPY go.* ./
RUN go mod download

# COPY All things inside the project and build
COPY . .
RUN go build -o /project/go-docker/build/myapp .

FROM alpine:latest
COPY --from=builder /project/go-docker/build/myapp /project/go-docker/build/myapp

EXPOSE 8080
ENTRYPOINT [ "/project/go-docker/build/myapp" ]

The difference from the first one:

  • FROM golang:1.16-alpine as builder , we will use golang:1.16-alpine and tag it as builder that later on will be used.
  • FROM alpine:latest , we will create a new base image from alpine .
  • COPY --from=builder /project/go-docker/build/myapp /project/go-docker/build/myapp , copy the build binary file into the new alpine image and run it later on.
    The image size of this Dockerfile is way smaller than the previous image.

FROM Scratch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM golang:1.16-alpine as builder

WORKDIR /project/go-docker/

# COPY go.mod, go.sum and download the dependencies
COPY go.* ./
RUN go mod download

# COPY All things inside the project and build
COPY . .
RUN go build -o /project/go-docker/build/myapp .

FROM scratch
COPY --from=builder /project/go-docker/build/myapp /project/go-docker/build/myapp

EXPOSE 8080
ENTRYPOINT [ "/project/go-docker/build/myapp" ]

And for the last Dockerfile, we only change the alpine base image into scratch . Scratch is an empty image, so once the container running, we can’t exec into the container because it doesn’t even have a shell command.

The image is slightly smaller than the alpine base image.

try to run the image by using docker run -d -p 8080:8080 test-go-docker:latest , it will forward port 8080 from the container to our 8080 port and access the http://localhost:8080 .

Conclusions

Personally, I will choose the second Dockerfile . Why? because the size is small and it still has several commands and a shell command so we can docker exec into the container and access it. If we use the scratch base image, it will be hard for us to debug our running container because we can’t exec into it.

That’s all for this article about Docker with Go Programming, hope you have a nice day :).