Continuously Deploy your Golang binaries using CircleCI and Heroku Docker
On the 30th of July 2016, at a gophers meetup in Nairobi, I decided to give a talk on deploying Go apps to Heroku using Docker. To make it more interesting, we were to setup a continuous delivery pipeline via CircleCI. Though, I haven’t used Heroku Docker before, I was pretty confident in my ability to easily navigate any issues I might face with Docker since I frequently deploy docker containers.
It was not as easy as I initially thought. This was because heroku docker is still in beta and there were very few resources out there on it. We spent a lot of time troubleshooting and fine tuning the pipeline to work correctly. Eventually, we were able to build a pipeline that worked perfectly. In this post, I will be walking you through how to setup heroku docker pipeline for your Go apps.
Why Heroku Docker
Go apps can be built to produce statically linked binaries free of external dependencies. With this small binaries, we can package a Go app as a docker image less than 10mb in size. Check out this post on Building Docker Images for Static Go Binaries for more information about building small docker images for Golang.
Heroku with it’s awesome tool set has made it possible to easily deploy apps. Just git push and everything works. Won’t it be nice to take advantage of it and still run your apps as docker containers? Won’t it be nice to just use the compiled binaries/artifact instead of pushing all our source code to heroku which will end up creating a very big slug. Won’t it be nice to take advantage of CI servers like CircleCI to automate all these while we git push as usual. Heroku Docker makes all of these possible.
To setup continous delivery via CircleCI, we will need to create 2 files(Dockerfile and circle.yml).
Open any of your existing golang project and create a Dockerfile. Copy the snippet below into the file.
The above Dockerfile is very simple.
- Use alpine:3.4 as our base image. Alpine is a very small(2MB), simple and secure linux image based on busybox. Check it out here.
- Install bash using alpine package manager(apk). This is needed to use heroku run command on the deployed container.
- Add the compiled binary. I called mine artifact. You can call yours anything.
- Execute the binary.
Heads up on Heroku Container Runtime
- The web process must listen for HTTP traffic on PORT env variable, which is set by Heroku. Only HTTP requests are supported.
- EXPOSE, VOLUME, HEALTHCHECK, SHELL, STOP SIGNAL commands are not supported in heroku container runtime.
- CMD is required while ENTRYPOINT is optional.
Setting up CircleCI
To setup up CI/CD via CircleCI, create a circle.yml file with the following configuration.
The docker file consist of different sections:
Machine: Since we will be building our docker image and pushing to heroku container registry from CircleCI, we need to turn on docker service which is off by default.
Environment Setup: augment build environment, by setting up an environment variable (IMPORT_PATH and GOPATH) for use in later steps.
Build Directory: Set build_dir to the right location in the GOPATH. All other command will be executed from that directory.
Checkout: CircleCI will clone repos into $HOME/$CIRCLE_PROJECT_REPONAME. However, go wants this folder to be in the $IMPORT_PATH located inside the $GOPATH. So we move the current repository into the right location in the GOPATH which we do with the following steps:
The rsync command will synchronize the current repository into the correct location in the import path. The options passed mean:
- -a ( — archive) is shorthand for a number of other arguments, but most importantly makes the sync recursive and preserves permissions, owners, etc.
- -z ( — compress) compresses data during transfer.
- -C ( — cvs-exclude) excludes various unneeded files.
- — delete removes files in the import path which have been deleted.
At this point, the repository is fully installed into the GOPATH.
Dependencies: If you are using godep to manage your dependencies, you’ll need to override the default steps. As a prerequisite, you’ll need to also install godep itself.
Test: Runs the test command for the project. Default command is go test ./…
Deployment: The deployment section has 4 commands viz:
- Install the container-registry plugin
- Login to the registry. We can decide to login to the registry directly with docker using: docker login — email=_ — username=_ — password=$(shell heroku auth:token) registry.heroku.com
- Generate the golang binary
- Build and push the docker image. Running the last command is equivalent to running:
You can run heroku container:push command from your local machine to push directory to heroku.
Heroku with docker is cool and setup like this gives us the same git push experience but it’s not necessarily needed in a lot of setup. If I am building a rails app for example, I won’t bother with it. This is because at the end of the day, heroku creates a slug(similar to a docker image) for your app. With docker, you are in control of the image that will be generated. But with more control comes greater responsibility.
I created a sample project for this post and it’s available on github. Do check it out and take a look at the circle.yml, Makefile and Dockerfile files.
If you liked this, click the💚 below so other people will see this here on Medium. Also, if you have any question or observation, use the comment section to share your thoughts/questions.