Building and Packaging in Go
Tags: go, docker, beginners
Published: 2021-12-20 05:15:04


## Building and Packaging 101 in Go



Packaging is probably as important as writing the code itself. If you get as confused as me looking at a Go project with a Makefile that has a ton of Latin commands inside it and a DockerFile using that Makefile, this might help.

### Building Go files

If you’re new to Go, you might be running your Go application using go run . . Although this is not the recommended way to run it in production or for that matter anywhere other than your local. You should build your application using go build which creates executables that you can run later on. *Typically a Makefile is used to do all these. ‘make’ is a program that runs a Makefile. *‘make’ handles builds efficiently. Using ‘make’, you can ensure that only the files that have been modified since the last build and those which are dependent on the changed files are compiled. ‘make’ uses the timestamp of the file to check if the file has been modified and whether it has to be recompiled or not.

The makefile of my project looked like this (explanations are followed by #):

When we run **make** or **make all** on the CLI, two executables — app1exec and app2exec will be created in the current working directory.

    PWD := $(shell pwd) #variable declarations
    GOPATH := $(shell go env GOPATH)
    source := $(wildcard *.go **/*.go */*/*.go)
    IMAGES:=imageapp1 imageapp2
    OTHER_FLAGS:=-tags musl

    all: app1 app2 #these targets are run if you run `make` or `make #all` from the CLI

    deps:  #dependencies I'm downloading gobin and a linter here
    @GO111MODULE=off go get -u github.com/myitcv/gobin
    @$(GOPATH)/bin/gobin github.com/golangci/golangci-lint/cmd/golangci-lint@v1.24.0

    test: #all tests are run if I run `make run` on the CLI
    go test ./...

    build: #this is more like a check whether everything can be built #because go build ./... discards the results after running
    go build ./...

    run: #to run locally
    go run github.com/shashankks0987/app1 & \
    go run github.com/shashankks0987/app2

    app1: $(source)  #the $(source) tells Makefile the dependent #project directories. If no file has changed in these, it will not 
    #run this job
    @echo "Building app1 to $(PWD)/app1..."
    @CGO_ENABLED=1 GO111MODULE=on go build $(OTHER_FLAGS) -o app1exec github.com/shashankks0987/app1

    app2: $(source)
    @echo "Building app2 to $(PWD)/app2..."
    @CGO_ENABLED=1 GO111MODULE=on go build $(OTHER_FLAGS) -o app2exec github.com/shashankks0987/app2

    docker:  #this is what you'll use to dockerize your code 
    @echo "Making docker image"

    for image in $(IMAGES); do\
    echo $$image;\
    docker build . --target $$image -t $$image;\
    done

CGO_ENABLED=1 leads to faster, smaller builds & runtimes - as it can dynamically load the host OS's native libraries (e.g. glibc, DNS resolver, etc.) - when running on the build OS. This is ideal for local rapid development.
This is a good [resource](https://stackoverflow.com/questions/64531437/why-is-cgo-enabled-1-default) to decide whether you should enable it or not.

There are various ways to ship your code — binaries, executables, docker images etc.., . Using docker images might be the most versatile of them all as docker makes sure your code runs the same way irrespective of the OS and other such system specifics.

Although you can draw some ideas from this DockerFile, you should know what works best for your project:

    FROM golang:1.17-alpine AS build_base
    RUN  apk add make git gcc musl-dev
    # Set the Current Working Directory inside the container
    WORKDIR /tmp/app
    COPY go.mod ./go.mod
    RUN go mod download
    COPY . .
    RUN make deps
    RUN make

    FROM alpine:3.9 as app1
    LABEL name=app1
    RUN apk add ca-certificates
    COPY --from=build_base /tmp/app/app1exec /app/app1
    RUN adduser -D -H -u 1801 user1
    USER 1801
    CMD ["/app/app1"]

    FROM alpine:3.9 as app2
    LABEL name=app2
    RUN apk add ca-certificates
    COPY --from=build_base /tmp/app/app2exec /app/app2
    RUN adduser -D -H -u 1801 user1
    USER 1801
    CMD ["/app/app2"]

### What is happening in the above DockerFile:

1) We build a base container that copies source code, downloads dependencies, and uses the makefile to create the required executables.

2) The other two images that follow just copy the executable from the base image.

3) app1 and app2 are now dockerized and ready to run!

I hope this read gives you a fair idea of how to build and package files in Go and also not panic (like I did) the next time you see these on a codebase.