How to build tiny Golang docker images with Gitlab-CI
Building tiny docker images for Golang applications with Gitlab
This post will show how to build Golang docker containers using Gitlab-CI. It’s the sequel of my previous posts about building and optimizing docker containers.
I’ve written a small microservice in Golang, and I needed to configure my GitLab to auto build it. It uses Glide as a package manager for Go.
My directory tree for this project looks like this:
> $ tree
├── Dockerfile
├── build_base.sh
├── glide.lock
├── glide.yaml
├── handlers.go
├── db.go
├── logger.go
├── main.go
├── middlewares.go
├── utils.go
├── deploy
│ └── <kubernetes-deploy-files>
├── release
│ └── Dockerfile
└── vendor
├── github.com
│ └── ...
├──gopkg.in
└── ...
Here I have some *.go files with my code. deploy directory with Kubernetes manifests. vendor folder is for dependencies. glide.lock and glide.yaml is used by the glide.
I have 2 docker files here, one in the top level and one is inside release directory. First one is to install and build application. The second is to run. Having separate docker images for build and run allow me to have tiny container at the end.
Dockerfile for build
So I needed docker image with go and glide installed to be able to build my application. I found 2 or 3 golang-glide images, but they are based on ubuntu and weighed around 700Mb.
So I created just another one, but with black jack and … based on alpine and with the latest versions of Golang and glide.
I’m using it as a base image for my builder.
FROM lwolf/golang-glide:0.12.3
ENV APP_PATH=/go/src/gitlab-path/repository-name
RUN mkdir -p $APP_PATH
WORKDIR $APP_PATH
COPY glide.yaml glide.yaml
COPY glide.lock glide.lock
RUN glide install -v
VOLUME ["/build"]
ADD . $APP_PATH
CMD GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o /build/app
It’s pretty simple and straightforward:
- set application path
- add glide files
- install dependencies
- build the application and put into the volume.
Dockerfile for production
This one is really simple. It based on the tiny image from iron with added binary from the previous step.
FROM iron/go
ENTRYPOINT ["/app"]
ADD app /
Resulting container is 17Mb in size. Which is great, comparing to 300-600Mb python images.
Automation
All we have to do now is to write .gitlab-ci.yml file to do all this on each push.
.gitlab-ci.yml and build_base.sh are almost as in the previous post.
# .gitlab-ci.yml
image: docker:latest
services:
- docker:dind
stages:
- build
variables:
REGISTRY: "docker-registry.example.com"
CONTAINER_IMAGE: ${REGISTRY}/${CI_PROJECT_NAME}:${CI_BUILD_REF_NAME}_${CI_BUILD_REF}
build:
stage: build
before_script:
- docker login -u gitlab-ci -p $DHUB_ROCKS_PASSWORD $REGISTRY
script:
- sh build_base.sh gitlab-ci ${DHUB_ROCKS_PASSWORD} ${REGISTRY}
- docker run --rm -v $PWD/release:/build ${REGISTRY}/${CI_PROJECT_NAME}:${CI_BUILD_REF_NAME}-builder-$(md5sum glide.lock | cut -d' ' -f1)
- cd release
- docker build -t ${CONTAINER_IMAGE} .
- docker push ${CONTAINER_IMAGE}
# build_base.sh
#!/usr/bin/env bash
username=${1}
password=${2}
registry=${3}
requirements_hash="$(md5sum glide.lock | cut -d' ' -f1)"
curl -u ${username}:${password} https://${registry}/v2/${CI_PROJECT_NAME}/tags/list | grep ${requirements_hash}
if [ $? -ne 0 ]; then
tag=${registry}/${CI_PROJECT_NAME}:${CI_BUILD_REF_NAME}-builder-${requirements_hash}
echo "going to build new base image ${tag}"
docker build -t ${tag} -f Dockerfile .
docker push ${tag}
fi
First I was using hash from the glide.lock file instead of calculation it. But it turned out that it’s not always updated.
Conclusion
As a result, I have a pretty fast build right now: 1:30m for usual builds and 3:00m on requirements update.
The size of the resulting image is also great - 17mb. It could be reduced for another few megs by switching from iron/go base image to scratch. But I don’t need it now.
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Email