Experiments with building Go in Docker on Mac
There are a number of examples knocking around, but piecing them together into something working took longer than it should've, hence this repo.
First step is to read up a bit on how to build outside of Docker (but not actually install anything). Digital Ocean have a good intro here. This overview of the go
command is useful to read too. And this about go get
.
So the ubiquitous hello.go is:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Executed with
go run hello.go
which will fetch the packages needed and install them locally.
To create an executable:
go build hello.go
There's also a go install hello.go
to put exe files into $GOPATH/bin etc
Note that you can follow these commands with ./..
which is like a Go / wildcard.
The hello.go example pulls in a library from "github.com/golang/example/stringutil"
so we need to initialise a module before running with an extra go get hello
or go mod init hello
before the run command above.
So enough background, onto a much better way to build and run Go, using the containerisation powers of Docker. There are a few resources like the official golang image. There are some useful bits in here and here too.
This was probably the most useful Docker + Golang resource covering more of the cross-compilation options that will allow us to compile and build in Docker and generate a Mac/OSX executable.
First step is to create a Dockerfile. You can run the compile build etc commands from the Dockerfile (commented out in this repo), and then build the Docker image from the command line with
docker build . -t nicksgolang
Next run the image with
docker run -it --rm --name insert-random-container-name nicksgolang
This will open an interactive shell in the docker container based on the nicksgolang
Docker image.
You can run the commands listed at the top of this README now .
We could install nano in the image to edit the hello.go
inside the shell, or more easily mount the local drive and edit in eg VSCode on Mac. So to do this we instead run
docker run -it --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp --name insert-random-container-name nicksgolang
Now we can edit the message in hello.go
and rebuild in the Docker shell and see the change.
Incidentally inside the docker shell, you can see that go get hello
pulls the stringutil and places it in /go/src/github.com/golang/example/stringutil
.
We haven't specifically set a go path, so it defaults to /go
.
You will have noticed that the Dockerfile has a couple of environment variables set, you can see these in the shell by entering printenv
, they are GOOS
and GOARCH
.
We have them set to darwin
and amd64
so the generated ./hello
binary is executable in a Mac terminal, but not in the Docker shell. Hence we have cross compiled.
Finally, we want to use the power of docker-compose to make it easier to manage our containers. For this we add a docker-compose.yml
file which includes the volume mapping and a command line, in this case:
command: sh -c "go get -d -v ./... && go build -v ./... "
Which gets the dependencies and builds the cross compiled binary for us. If we've modified the Dockerfile, rebuild the image with
Docker build . -t nicksgolang
Then run with (--build flag rebuilds too)
docker-compose up
Check in the current directory for the freshly generated executable.