- Introduction
- installation
- Go official image
- A sample application
- Move the application to Docker
- Trim the Docker image
- Multi-stage construction
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 havedocker
Commands 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 asq
Query 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 get
I 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 firstgolang
Docker 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-l
The 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 get
The 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 -lq
And 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 runfindlinks
In our orderfindlinks
With 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.100
Is 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.3
Image 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 onefindlinks
Binary 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 build
On 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.8MBRun the image with
$ docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks
More go tutorials:
- Using NGINX Reverse Proxy to serve Go services
- Making a copy of a struct in Go
- The basics of a Go Web Server
- Sorting a map type in Go
- Go pointers in a nutshell
- Go Tags explained
- Go Date and Time Formatting
- JSON processing with Go
- Go Variadic Functions
- Go Strings Cheat Sheet
- The Go Empty Interface Explained
- Debugging Go with VS Code and Delve
- Named Go returns parameters
- Generating random numbers and strings in Go
- Filesystem Structure of a Go project
- Binary Search Algorithm Implemented in Go
- Using Command Line Flags in Go
- GOPATH Explained
- Build a Command Line app with Go: lolcat
- Building a CLI command with Go: cowsay
- Using Shell Pipes with Go
- Go CLI tutorial: fortune clone
- List the files in a folder with Go
- Use Go to get a list of repositories from GitHub
- Go, append a slice of strings to a file
- Go, convert a string to a bytes slice
- Visualize your local Git contributions with Go
- Getting started with Go CPU and memory profiling
- Solving the "does not support indexing" error in a Go program
- Measuring execution time in a Go program
- Building a Web Crawler with Go to detect duplicate titles
- Go Best Practices: Pointer or value receivers?
- Go Best Practices: Should you use a method or a function?
- Go Data Structures: Set
- Go Maps Cheat Sheet
- Generate implementations for generic types in Go
- Go Data Structures: Dictionary
- Go Data Structures: Hash Table
- Implement Events Listeners in Go through Channels
- Go Data Structures: Stack
- Go Data Structures: Queue
- Go Data Structures: Binary Search Tree
- Go Data Structures: Graph
- Go Data Structures: Linked List
- The complete guide to Go Data Structures
- Comparing Go Values
- Is Go object oriented?
- Working with a SQL Database in Go
- Using environment variables in Go
- Go tutorial: REST API backed by PostgreSQL
- Enabling CORS on a Go Web Server
- Deploying a Go Application in a Docker Container
- Why Go is a powerful language to learn as a PHP developer
- Go, remove the io.Reader.ReadString newline char
- Go, how to watch changes and rebuild your program
- Go, count the months since a date
- Accessing HTTP POST parameters in Go