Install Go In Docker

/ Comments off
  1. # Build the Go app RUN go build -o./out/go-sample-app. # This container exposes port 8080 to the outside world EXPOSE 8080 # Run the binary program produced by `go install` CMD './out/go-sample-app' The Dockerfile will also automatically take advantage of the Codefresh distributed docker cache. Create a multi-stage Docker image for GO.
  2. Docker pull bettercap/bettercap To pull latest source code build of the image: docker pull bettercap/dev To run: docker run -it -privileged -net=host bettercap/bettercap -h Compiling from Sources. In order to compile bettercap from sources, make sure that: You have a correctly configured Go = 1.8 environment.
  1. Install Go In Dockerfile
  2. Install Go In Docker Software

When joining a development team, it takes some time to become productive. This is usually a combination of learning the code base and getting your environment setup. Often there will be an onboarding document of some sort for setting up your environment but in my experience, this is never up to date and you always have to ask someone for help with what tools are needed.

Docker + Golang = Docker can be useful when working with Go code. For instance, I’ll show you how to compile Go code with different versions of the Go toolchain, how to cross-compile to a different platform (and test the result!), or how to produce really small container images.

This problem continues as you spend more time in the team. You’ll find issues because the version of the tool you’re using is different to that used by someone on your team, or, worse, the CI. I’ve been on more than one team where “works on my machine” has been exclaimed or written in all caps on Slack and I’ve spent a lot of time debugging things on the CI which is incredibly painful.

Many people use Docker as a way to run application dependencies, like databases, while they’re developing locally and for containerizing their production applications. Docker is also a great tool for defining your development environment in code to ensure that your team members and the CI are all using the same set of tools.

We do a lot of Go development at Docker. The Go toolchain is great– providing fast compile times, built in dependency management, easy cross compiling, and strong opinionation on things like code formatting. Even with this toolchain we often run into issues like mismatched versions of Go, missing dependencies, and slightly different configurations. A good example of this is that we use gRPC for many projects and so require a specific version of protoc that works with our code base.

This is the first of a series of blog posts that will show you how to use Docker for Go development. It will cover building, testing, CI, and optimization to make your builds quicker.

Start simple

Let’s start with a simple Go program:

package main
import'fmt'
funcmain() {
fmt.Println('Hello world!')
}

You can easily build this into a binary using the following command:
$ go build -o bin/example .

The same can be achieved using the following Dockerfile:

FROMgolang:1.14.3-alpine AS build
WORKDIR/src
COPY. .
RUNgo build -o /out/example .
FROMscratchAS bin
COPY--from=build /out/example /

This Dockerfile is broken into two stages identified with the AS keyword. The first stage, build, starts from the Go Alpine image. Alpine uses the musl C library and is a minimalist alternative to the regular Debian based Golang image. Note that we can define which version of Go we want to use. It then sets the working directory in the container, copies the source from the host into the container, and runs the go build command from before. The second stage, bin, uses a scratch (i.e.: empty) base image. It then simply copies the resulting binary from the first stage to its filesystem. Note that if your binary needs other resources, like CA certificates, then these would also need to be included in the final image.

As we are leveraging BuildKit in this blog post, you will need to make sure that you enable it by using Docker 19.03 or later and setting DOCKER_BUILDKIT=1 in your environment. On Linux, macOS, or using WSL 2 you can do this using the following command:
$ export DOCKER_BUILDKIT=1

On Windows for PowerShell you can use:
$env:DOCKER_BUILDKIT=1

Or for command prompt:
set DOCKER_BUILDKIT=1

To run the build, we will use the docker build command with the output option to say that we want the result to be written to the host’s filesystem:
$ docker build --target bin --output bin/ .

You will then see that we have the example binary inside our bin directory:

Install Go In Dockerfile

Above we have two different stages for Unix-like OSes (bin-unix) and for Windows (bin-windows). We then add aliases for Linux (bin-linux) and macOS (bin-darwin). This allows us to make a dynamic target (bin) that depends on the TARGETOS variable and is automatically set by the docker build platform flag.

This allows us to build for a specific platform:

Our updated Makefile has a PLATFORM variable that you can set:

all: bin/example
PLATFORM=local
.PHONY: bin/example
bin/example:
@docker build . --target bin
--output bin/ --platform ${PLATFORM}

This means that you can build for a specific platform by setting PLATFORM:
$ make PLATFORM=windows/amd64

You can find the list of valid operating system and architecture combinations here: https://golang.org/doc/install/source#environment.

Shrinking our build context

By default, the docker build command will take everything in the path passed to it and send it to the builder. In our case, that includes the contents of the bin/ directory which we don’t use in our build. We can tell the builder not to do this, using a .dockerignore file:

bin/*

Since the bin/ directory contains several megabytes of data, adding the .dockerignore file reduces the time it takes to build by a little bit.

Similarly, if you are using Git for code management but do not use git commands as part of your build process, you can exclude the .git/ directory too.

What’s next?

This post showed how to start a containerized development environment for local Go development, build an example CLI tool for different platforms and how to start speeding up builds by shrinking the build context. In the next post of this series, we will add dependencies to make our example project more realistic, look at caching to make builds faster, and add unit tests.

You can find the finalized source this example on my GitHub: https://github.com/chris-crone/containerized-go-dev

If you’re interested in build at Docker, take a look at the Buildx repository: https://github.com/docker/buildx

Read the whole blog post series here.

Using Codefresh pipelines

Codefresh can work with Go projects of any version using built-in modules or any other dependency mechanism.

The example golang project

You can see the example project at https://github.com/codefresh-contrib/golang-sample-app. The repository contains a simple Golang web application including unit tests. There are 3 Dockerfiles available:

  • Simple Dockerfile (with old Go version that requires GOPATH building)
  • Dockerfile with Go modules (optimized for Docker caching)
  • Multi-stage Dockerfile (with Go modules and unit tests)

Let’s see these workflows in order.

Simple Docker image pipeline

The most simple pipeline that you can create is just two steps. A clone step to fetch the code and a build step to create a Docker image.

codefresh.yml

Once you run this pipeline Codefresh will create a Docker image for the Golang application:

The big advantage of this workflow is that the Dockerfile you use can define any Go version and dependency tool. As long as the Dockerfile is self-contained (i.e. it compiles GO on its own), the pipeline will work as expected.

In the example application, the simple (unoptimized) Dockerfile has an old Go version that still requires GOPATH folders.

Dockerfile

Install Go In Docker Software

Run unit tests as part of the pipeline

If you want to run Go specific steps in your pipeline, you can use freestyle steps with any GO image that you want. If your GO application is using GO modules, this is even easier as you don’t need to place the application into a specific GOPATH compliant directory first.

This pipeline is running unit tests as a separate step and then builds the docker image.

codefresh.yml

If the unit tests fail, then the docker image will never be created (Codefresh automatically stops a pipeline when there is an error).

Notice that in this case we have added module support in the Go application. The new Dockerfile is the following:

Dockerfile

The Dockerfile will also automatically take advantage of the Codefresh distributed docker cache.

Install Go In Docker

Create a multi-stage Docker image for GO

Especially with Go applications, the recommended way to create Docker images is with multi-stage builds. This makes the resulting Docker image as compact as possible.

You can also embed unit tests in the Docker creation process, which guarantee the correctness of image (integration tests are best kept in the pipeline).

Here is the new Dockerfile:

Dockerfile

Codefresh has native support for multi-stage builds. The pipeline is the same as the first one with just two steps.

codefresh.yml

You should see a much smaller Docker image at the end.

Viewing Docker images

If you look at your Docker registry dashboard created the advantages of the multi-stage build are very clear:

We recommend using Go modules and multi-stage builds in your Go projects.

What to read next