Deploy Go application in Docker container

Introduction

If you have never heard of Docker, but it is unlikely, then the first thing you should know is that Docker allows you to run applications independently, and can separate concerns, but still allows them to be carried out with the outside world Communication and interaction.

And you should know that everyone uses it, and every major cloud provider provides a solution dedicated to running containers, so you should learn it!

installation

Installation will vary depending on your system, so please checkhttps://www.docker.com/get-docker.

I assume you have installed Docker and havedockerCommands available in your shell.

Go official image

Docker maintains a list of official images in many different languages, and Go is no exception because it is part of the original languageOfficial image release back in 2014.

The official image gallery can be found at the following locationhttps://hub.docker.com/_/golang/. There are many tags that can identify both the Go version and the operating system you want to obtain.

A sample application

For example, I will deploy a small Go application in a Docker container. It listens on port 8000 and treats the web page asqQuery parameters, extract them and print the links found:

findlinks.go

package main

import ( “fmt” “log” “net/http”

<span style="color:#e6db74">"golang.org/x/net/html"</span>

)

func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(“0.0.0.0:8000”, nil)) }

func handler(w http.ResponseWriter, r *http.Request) { url := r.URL.Query().Get(“q”) fmt.Fprintf(w, “Page = %q\n”, url) if len(url) == 0 { return } page, err := parse(“https://” + url) if err != nil { fmt.Printf(“Error getting page %s %s\n”, url, err) return } links := pageLinks(nil, page) for _, link := range links { fmt.Fprintf(w, “Link = %q\n”, link) } }

func parse(url string) (*html.Node, error) { fmt.Println(url) r, err := http.Get(url) if err != nil { return nil, fmt.Errorf(“Cannot get page”) } b, err := html.Parse(r.Body) if err != nil { return nil, fmt.Errorf(“Cannot parse page”) } return b, err }

func pageLinks(links []string, n *html.Node) []string { if n.Type == html.ElementNode && n.Data == “a” { for _, a := range n.Attr { if a.Key == “href” { links = append(links, a.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { links = pageLinks(links, c) } return links }

Example usage:

Move the application to Docker

I put the application inhttps://github.com/flaviocopes/findlinks.

usego getI can easily download and install it, usinggo get github.com/flaviocopes/findlinks.

Run

docker run golang go get -v github.com/flaviocopes/findlinks

Will be downloaded firstgolangDocker image (if not already installed), it will fetch the repository and scan for other dependencies not included in the standard library. under these circumstances,golang.org/x/net/html.

$ docker run golang go get -v github.com/flaviocopes/findlinks
github.com/flaviocopes/findlinks (download)
Fetching https://golang.org/x/net/html?go-get=1
Parsing meta tags from https://golang.org/x/net/html?go-get=1 (status code 200)
get "golang.org/x/net/html": found meta tag main.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at https://golang.org/x/net/html?go-get=1
get "golang.org/x/net/html": verifying non-authoritative meta tag
Fetching https://golang.org/x/net?go-get=1
Parsing meta tags from https://golang.org/x/net?go-get=1 (status code 200)
golang.org/x/net (download)
golang.org/x/net/html/atom
golang.org/x/net/html
github.com/flaviocopes/findlinks

This command creates a container and runs it. We can use checkdocker ps -l(This-lThe option tells Docker to list the latest containers running):

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
343d96441f16        golang              "go get -v github...."   3 minutes ago       Exited (0) 2 minutes ago                       mystifying_swanson

When the container exits,go getThe command is complete.

Docker just built an on-demand image and ran it. To run it again, we will need to repeat the process, but an image can help us: let's create an image from this container now so that we can run it later:

docker commit $(docker ps -lq) findlinks

The above command uses the following command to get the last container IDdocker ps -lqAnd submit the image. The image has been installed, you can usedocker images findlinks:

$ docker images findlinks
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
findlinks           latest              4e7ebb87d02e        11 seconds ago      720MB

We can runfindlinksIn our orderfindlinksWith image

docker run -p 8000:8000 findlinks findlinks

That's it! Our application will now respondhttp://192.168.99.100:8000/, where is it192.168.99.100Is the IP address of the Docker container.

You can test the callhttp://192.168.99.100:8000/?q=flaviocopes.com, It will output the same output as when running the application locally:

Page = "flaviocopes.com"
Link = "https://flaviocopes.com/index.xml"
Link = "https://twitter.com/flaviocopes"
Link = "https://github.com/flaviocopes"
Link = "https://stackoverflow.com/users/205039/flaviocopes"
Link = "https://linkedin.com/in/flaviocopes/"
Link = "mailto:[email protected]"
Link = "/"
Link = "/page/contact/"
Link = "/page/about/"
Link = "https://flaviocopes.com/golang-tutorial-rest-api/"
Link = "https://flaviocopes.com/golang-environment-variables/"
Link = "https://flaviocopes.com/golang-sql-database/"
Link = "https://flaviocopes.com/golang-is-go-object-oriented/"
Link = "https://flaviocopes.com/golang-comparing-values/"
Link = "https://flaviocopes.com/golang-data-structures/"
Link = "https://flaviocopes.com/golang-data-structure-binary-search-tree/"
Link = "https://flaviocopes.com/golang-data-structure-graph/"
Link = "https://flaviocopes.com/golang-data-structure-linked-list/"
Link = "https://flaviocopes.com/golang-data-structure-queue/"
Link = "https://flaviocopes.com/golang-data-structure-stack/"
Link = "https://flaviocopes.com/golang-event-listeners/"

Trim the Docker image

This strategy is now deprecated, and it is recommendedMulti-stage construction

The problem with the above results is,The image is very large: 720MB is not really acceptable for this simple program, please remember that this is a very simple program. We may have to deploy thousands of application instances, and this size will not work properly.

Why is the image so big? Because what happens is that the Go application is compiled inside the container. Therefore, the image requires the Go compiler to be installed. Of course there are compilers, GCC and everything needed for the entire Linux distribution (Debian Jessie). It downloads Go and installs it, compiles the application and runs it.

It's so fast, we don't even realize it. But we can do better. how is it? I use what I learnedBuild the smallest Docker container for Go applicationsafterNick Gautier

We tell Docker to rungolang:1.8.3Image andStatic compilationOur application disables CGO, which means that the image does not even need the C library that is usually accessed when dynamically linked, you can use:

docker run --rm -it -v "$GOPATH":/gopath -v "$(pwd)":/app -e "GOPATH=/gopath" -w /app golang:1.8.3 sh -c 'CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags="-s" -o findlinks'

We now have onefindlinksBinary files in the folder:

$ ll
.rw-r--r--   77 flavio 17 Aug 18:57 Dockerfile
.rwxr-xr-x 4.2M flavio 17 Aug 19:13 findlinks
.rw-r--r-- 1.1k flavio 12 Aug 18:10 findlinks.go

$ file findlinks findlinks: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

Note that this is of course not the same file I gotgo buildOn OSX, it is a Linux-ready binary file:

$ file findlinks
findlinks: Mach-O 64-bit executable x86_64

Then, we create a Dockerfile and tell Docker to useIron/base, Very light image:

FROM iron/base
WORKDIR /app
COPY findlinks /app/
ENTRYPOINT ["./findlinks"]

Now we can build the image, label itflaviocopes/golang-docker-example-findlinks:

docker build -t flaviocopes/golang-docker-example-findlinks .

And run it:

docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks

The output is the same as before, but this time the image is not 720MB, but 11.1MB

REPOSITORY                                    TAG                 IMAGE ID            CREATED             SIZE
flaviocopes/golang-docker-example-findlinks   latest              f32d2fd74638        14 minutes ago      11.1MB
findlinks                                     latest              c60f6792b9f3        20 minutes ago      720MB

Multi-stage construction

This part was added because Reddit's comments pointed me to a multi-stage build, which is a recent addition.Docker 17.05

Multi-stage constructionAllows us to have a lightweight image very easily without having to compile a binary file and then run it separately. This is the Dockerfile to be put into the application:

FROM golang:1.8.3 as builder
WORKDIR /go/src/github.com/flaviocopes/findlinks
RUN go get -d -v golang.org/x/net/html
COPY findlinks.go  .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o findlinks .

FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/flaviocopes/findlinks/findlinks . CMD ["./findlinks"]

run

$ docker build -t flaviocopes/golang-docker-example-findlinks .

Generate a lightweight (10.8MB) image:

$ docker images
REPOSITORY                                    TAG                 IMAGE ID            CREATED             SIZE
flaviocopes/golang-docker-example-findlinks   latest              aa2081ca7016        12 seconds ago      10.8MB

Run the image with

$ docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks  

More go tutorials: