DEV Community

Cover image for Go, Docker and Gitlab easy config
Dmitrij T.
Dmitrij T.

Posted on

Go, Docker and Gitlab easy config

What it is about?

I've started to learn Go language few days ago, because of one service that I needed to prepare for one project. The service is about some image processing and delivery as static server. I'm gonna write short case study about this service later too. πŸ‘Œ

As I am a fan of containers I put development environment in Docker containers. I like Go language a lot and was able to write MVP really fast. When I was done I decided to get some inspiration about publishing Docker image of my app using Gitlab CI. To get some best practices or caveats maybe.

And I was really surprised how complicated (or outdated) configs I found.

Alt Text

I thought why I'm loosing time with those? The app is running in Docker container so I don't need to make platform-specific builds, inject ENVs to Gitlab PATH or other similar weird things...

I wrote it for myself and gonna show it here. I hope it will be useful for those who are interested in Go, Docker or microservices. ✌️

Simple Go app in Docker

I assume you guys have some basic knowledge about Docker or containerization as a whole at least. But I think many of you beginners will manage too. πŸ’ͺ

  1. Firstly, I will show project stucture so you get the context.
  2. Then I'm going to write how I prepared development docker environment for Go app.
  3. Finally I will demonstrate building production Go app docker image using Gitlab CI.

Project structure

Don't get me wrong, this structure is one out of thousands. And I'm not saying it's the best one or one that you should use. I just got used to this so I'm using it almost everywhere.

It will just get you to configuration files context, so that you know where the paths come from.

.
β”œβ”€β”€ bin # helper docker-compose scripts
β”‚   β”œβ”€β”€ build
β”‚   β”œβ”€β”€ go
β”‚   └── start
β”œβ”€β”€ dev-ops
β”‚   β”œβ”€β”€ dev.Dockerfile
β”‚   β”œβ”€β”€ docker-compose.yml
β”‚   └── prod.Dockerfile
β”œβ”€β”€ README.md
└── source # go source files
    β”œβ”€β”€ crop.go
    β”œβ”€β”€ delivery.go
    β”œβ”€β”€ go.mod
    β”œβ”€β”€ go.sum
    β”œβ”€β”€ main.go
    └── vendor # app's go dependencies source files
Enter fullscreen mode Exit fullscreen mode

Go development in Docker

We need to prepare dev image of our app.

I build it from golang alpine image, which is lighter and has everything I need.

You can see watcher command there. Watcher is a tool, as name says, that watches .go file changes and builds code when change detected. So we don't have to do it manually in terminal each time.

## ./dev-ops/dev.Dockerfile

FROM golang:1.15-alpine3.12

## Make app dir for source files
## Get git binary so that GO can download dependencies
RUN mkdir /app \
    && apk add git

## Set app folder as work directory (default context in Docker container)
WORKDIR /app

## get and install watcher
RUN go get github.com/canthefason/go-watcher \
    && go install github.com/canthefason/go-watcher/cmd/watcher

## Turn on Go modules, it allows easier dependency managment
## Copy files with modules (dependencies) requirements, so that GO knows what to download
## Reminds me package.json and yarn.lock in JS development
ENV GO111MODULE=on
ADD ./source/go.mod /app
ADD ./source/go.sum /app

## pull in all modules (dependencies)
RUN go mod download

## Add source files to image work directory
ADD ./source /app

## Command which applies when container from this image runs
CMD ["watcher"]
Enter fullscreen mode Exit fullscreen mode

There is also plenty of similar Go tools like Watcher. For example Gin or Fresh. Or you can also use Nodemon (for those who came from JS environment πŸ™‚)

And there is docker-compose file for running Docker container with our Go app.

## ./dev-ops/docker-compose.yml

version: '3'
services:
  ## ids is image delivery service (the service I wrote)
  ids:
    ## name of our image built from dev.Dockerfile above
    image: registry.gitlab.com/dashers/image-delivery-service/ids:dev
    container_name: dasher_ids
    ## sync your local source files with those in container
    volumes:
      - ../source/:/app/
...
Enter fullscreen mode Exit fullscreen mode

Go production in Docker & Gitlab CI

Now we prepare production Docker image.

Again it's built from golang alpine image for same reasons as before.

## ./dev-ops/prod.Dockerfile

FROM golang:1.15-alpine3.12

RUN apk add git

WORKDIR /go/src/gitlab.com/dashers/image-delivery-service

## same as in dev... add files with dependencies requierments
ENV GO111MODULE=on
ADD ./go.mod .
ADD ./go.sum .

## pull in any dependencies
RUN go mod download

## add source files
ADD . .

## our app will now successfully build with the necessary go dependencies included
## creates ./main binary executable file
RUN go build -o main .

## runs our newly created binary executable
CMD ["./main"]
Enter fullscreen mode Exit fullscreen mode

Easy right? Let's look at gitlab ci config.

I'm showing here just one stage - BUILD and PUBLISH. It builds our prod.Dockerfile and publishes image to Gitlab container registry, where it will be available for pull.

## ./gitlab-ci.yml

image: docker:stable

variables:
  # SET DEFAULT BEHAVIOR FOR CI
  # Disable submodules on CI, we are not using submodules
  GIT_SUBMODULE_STRATEGY: none
# important! we need to say gitlab that we run docker container in docker container..
services:
  - docker:dind

stages:
  - BUILD and PUBLISH

## template for build and publish stage
.build-and-publish_template: &build-and-publish_template
  stage: BUILD and PUBLISH
  ## I version project using commit tags. 
  ## Here I say I want to trigger the stage only if commit tag has format ids-x.x.x
  rules:
    - if: $CI_COMMIT_TAG =~ /^(ids)-[0-9]+\.[0-9]+\.[0-9]+$/
      when: always
    - when: never    
  before_script:
    # accept ids-0.0.1 => 0.0.1 | master => master
    - VERSION=$(if [ "$CI_COMMIT_TAG" == "" ]; then echo $CI_COMMIT_REF_NAME; else echo $CI_COMMIT_TAG |awk -F- '{print $2}'; fi)
    - echo $VERSION
        # login to gitlab container registry
    - echo -n $CI_REGISTRY_PASSWORD | docker login --username $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
  after_script:
    # clean up
    - docker logout $CI_REGISTRY

## build and publish stage (using the template above)
ids - build and publish: 
  <<: *build-and-publish_template
  script:
    # build our production image (prod.Dockerfile)
    # registry.gitlab.com/dashers/image-delivery-service/ids:x.x.x
    - >
      docker build \
        --network host \
        --tag ${CI_REGISTRY}/dashers/image-delivery-service/ids:${VERSION} \
        --file ${CI_PROJECT_DIR}/dev-ops/prod.Dockerfile \
        --rm \
        ${CI_PROJECT_DIR}/source

    # publish into docker registry
    - docker push ${CI_REGISTRY}/dashers/image-delivery-service/ids:${VERSION}
Enter fullscreen mode Exit fullscreen mode

That's it! πŸŽ‰

Now you can create docker-compose similar to that I've shown you (just without volume section) in your production server, use image from gitlab registry, run container and your Go app is ready and running.

If you want to see the whole project's configuration or maybe even use our service, you can find it in our Gitlab repo here.

Feel free to comment, feedback or question ✌️

Have a nice day!

Top comments (0)