How to make friends with Golang, Docker and GitLab CI
Tags: go, docker, gitlab
Published: 2018-10-26 16:38:48

---
title: How to make friends with Golang, Docker and GitLab CI
published: true
description: A simple way to build Golang apps with Docker in GitLab
tags: golang, go, docker, gitlab
cover_image: https://thepracticaldev.s3.amazonaws.com/i/jz1v8e2wrog01wo0ljyn.jpg
---

Let's start by demonstrating the simple app we will use as an example:

```golang
package main

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

var port = "8080"

func main() {
	log.Fatal(http.ListenAndServe(":"+port, router()))
}

func router() http.Handler {
	r := mux.NewRouter()
	r.Path("/greeting").Methods(http.MethodGet).HandlerFunc(greet)
	return r
}

func greet(w http.ResponseWriter, req *http.Request) {
	_, _ = w.Write([]byte("Hello, world!"))
}
```

Also, let's add a small test:

```golang
package main

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestRouter(t *testing.T) {
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/greeting", nil)
	router().ServeHTTP(w, req)

	expected := "Hello, world!"
	actual := w.Body.String()
	if expected != actual {
		t.Fatalf("Expected %s but got %s", expected, actual)
	}
}
```

This is pretty straightforward. One thing I want to emphasize is that we have an external dependency. To manage it (and future dependencies), we are going to use [dep](https://golang.github.io/dep/):

```bash
dep init -v -no-examples
```

Just to show everything works fine, we run tests, build and try our app locally:

```bash
$ go test ./...
ok  	gitlab.com/hypnoglow/example-go-docker-gitlab	0.016s

$ go build -o app .
$ ./app

# and in another terminal:
$ curl http://localhost:8080/greeting
Hello, world!
```


Seems simple. Now we want to dockerize our app. 

Question: how we will pass dependencies to the docker build process? 

- Option 1: pass `vendor` directory along with the source code using `COPY` command. 

- Option 2: install dependencies on build time using `RUN dep ensure`. 

The first option has a few benefits over second:

- We (usually) already have our vendors installed locally, why bother installing them again at the build time? This way we speed up the `docker build` process.
- In case of private dependencies, like your package in another private repository, you need to pass git credentials to the docker build process to make go tool able to fetch those dependencies. In 1st option, you don't have such problem.

Thus, we create the following Dockerfile:

```docker
FROM golang:1.10-alpine3.7 as build

WORKDIR /go/src/app

COPY . .

RUN go build -o app

FROM alpine:3.7

COPY --from=build /go/src/app/app /usr/local/bin/app

ENTRYPOINT ["/usr/local/bin/app"]
```

Next, we need a `.gitlab-ci.yml` file to run tests and build our image on push. How are we going to accomplish that tasks? 

Well, we need a job to install dependencies, because above we decided not to install them in Dockerfile. Even if we did, we need them installed to run our test. So, we create a job `dep` to install dependencies and store `vendor` directory as a GitLab artifact. In other jobs, we add `dep` job as a dependency, and GitLab will extract previously stored `vendor` right into our project directory.

```yaml
variables:
  PACKAGE_PATH: /go/src/gitlab.com/hypnoglow/example-go-docker-gitlab

stages:
  - dep
  - test
  - build

# A hack to make Golang-in-Gitlab happy
.anchors:
  - &inject-gopath
      mkdir -p $(dirname ${PACKAGE_PATH})
      && ln -s ${CI_PROJECT_DIR} ${PACKAGE_PATH}
      && cd ${PACKAGE_PATH}

dep:
  stage: dep
  image: golang:1.10-alpine3.7
  before_script:
    - apk add --no-cache curl git
    - curl -sSL https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 -o /go/bin/dep
    - chmod +x /go/bin/dep
    - *inject-gopath
  script:
    - dep ensure -v -vendor-only
  artifacts:
    name: "vendor-$CI_PIPELINE_ID"
    paths:
      - vendor/
    expire_in: 1 hour

test:
  stage: test
  dependencies:
    - dep
  image: golang:1.10-alpine3.7
  before_script:
    - *inject-gopath
  script:
    - go test ./...

build:
  stage: build
  dependencies:
    - dep
  image: docker:17
  services:
    - docker:dind
  script:
    - docker build -t app .
```

Finally, we check our pipeline:

![pipeline](https://thepracticaldev.s3.amazonaws.com/i/waox28fc4zv11x5fpazn.png)

We're done! Next steps, like pushing the built image to the docker registry, are left as an exercise for the reader. 🙂

The full example is available in the [GitLab repository](https://gitlab.com/hypnoglow/example-go-docker-gitlab).

Thanks! This was my 1st article on **dev.to**, I hope you enjoyed.

*I apologize for any grammatical and linguistic mistakes, as English is not my native language. Please fix me in comments if you spot a problem!* 🤓