Deploying Go Apps to Heroku with Docker

This post covers using the heroku docker cli plugin to deploy a Go application to Heroku. Only Linux and MAC OSX are supported until docker-compose supports MS Windows.

Prerequisites

Please take a look at the links above and make sure everything is installed as per the linked instructions.

Getting Started

Start by cloning the Go Websocket Chat Demo Heroku application, a simple websocket application using redis to pass messages between processes:

$ git clone https://github.com/heroku-examples/go-websocket-chat-demo.git
$ cd go-websocket-chat-demo

Note: It doesn’t matter where the code is located for this exercise as we’ll be doing everything inside of a docker container that will setup a $GOPATH for you.

The application is already prepared for Heroku as it contains both a Procfile, which tells Heroku what to run, and an app.json file containing meta-data about the app.

In the case of Heroku Docker, the important part of the app.json file is the image, mount_dir & addons keys, as seen below:

{
  "name": "Go Websocket Chat Demo",
  "image": "heroku/go:1.5",
  "mount_dir": "src/github.com/heroku-examples/go-websocket-chat-demo",
  "repository": "http://github.com/heroku-examples/go-websocket-chat-demol",
  "addons": [ "heroku-redis" ]
}

The image key is used to determine which docker image to use. The mount_dir key is used to determine where to mount your source code for the interactive docker-compose run shell target (more on that later). And the addons key tells Heroku which Addons the application requires. The addons currently supported by the Heroku Docker CLI plugin are: Heroku Postgresql (heroku-postgresql), Heroku Redis (heroku-redis), Redis Cloud (rediscloud), Mongolab (mongolab) & Memcached Cloud (memcachedcloud). Supported addons will be attached to your container and created on your heroku application if they don’t exist during docker:release.

Given this configuration we can initialize the app with the following command:

$ heroku docker:init

This creates two files: Dockerfile & docker-compose.yml. The Dockerfile is simple and just specifies the base heroku/go docker image. The docker-compose.yml file describes the containers required to run the application locally based on your addons and the contents of your Procfile.

Run Locally

Run the following command to start the application and it’s dependencies in local containers:

$ docker-compose up web

The first time this runs the docker images required to run the containers are constructed by pulling the required images from Docker Hub. These images are cached locally for re-use. The code is then copied into the container and compiled.

When the container is finished starting you’ll see some output like this:

$ docker-compose up web
...
web_1         | [negroni] listening on :8080
...
herokuRedis_1 | 1:M 28 Aug 21:05:40.442 * The server is now ready to accept connections on port 6379
web_1         | time="2015-08-28T21:06:36Z" level=info msg="Redis Subscription Received" channel=chat count=1 kind=subscribe

Open the app in a browser by running:

$ open http://$(docker-machine ip default):8080

Your web application and redis database are containerized, running inside your local Docker environment, connected to each other via Docker Compose, creating a sort of “local cloud” on your machine.

Deploying to Heroku

Create a Heroku application like so:

$ heroku create
Creating peaceful-tor-8674... done, stack is cedar-14
https://peaceful-tor-8674.herokuapp.com/ | https://git.heroku.com/peaceful-tor-8674.git
Git remote heroku added

Deploy the application to Heroku using the following command:

$ heroku docker:release
Remote addons:  (0)
Local addons: heroku-redis (1)
Missing addons: heroku-redis (1)
Provisioning heroku-redis...
Creating local slug...
Building web...
Step 0 : FROM heroku/go:1.5
# Executing 2 build triggers
Trigger 0, COPY . /app/.temp
Step 0 : COPY . /app/.temp
Trigger 1, RUN /app/.cache/gotools/bin/compile
Step 0 : RUN /app/.cache/gotools/bin/compile
 ---> Running in 0db7ad69945e
godep go install -tags heroku ./...
 ---> 53f3c75d186c
Removing intermediate container 0db7ad69945e
Removing intermediate container edb3fca8865a
Successfully built 53f3c75d186c
extracting slug from container...
creating remote slug...
language-pack: heroku-docker (heroku/go:1.5)
remote process types: { web: 'cd /app/user/src/github.com/heroku-examples/go-websocket-chat-demo && go-websocket-chat-demo-web' }
uploading slug [====================] 100% of 3 MB, 0.0s
releasing slug...
Successfully released peaceful-tor-8674!

That’s it! You’ll notice that a heroku-redis addon was added to the application because we haven’t added one yet and it’s an addon listed in the app.json file.

NOTE: Sometimes the heroku-redis instance isn’t immediately available. You can check availability with heroku redis, looking at the Status field. If the app started before the database is available messages sent via the web interface will not show up in the chat log and you will need to restart the application (heroku restart) after the redis isntance is available.

Open the website and talk to yourself and share the link with someone else to chat with them.

$ heroku open

A Local Shell

The plugin configures a shell process that you can use to get shell access to containers running your app. This is similar to one-off dynos on Heroku, and is handy for completing administrative tasks like database tasks, package vendoring, testing, etc.

You can also get a local shell into your application as well with:

$ docker-compose run shell
Building shell...
Step 0 : FROM heroku/go:1.5
# Executing 2 build triggers
Trigger 0, COPY . /app/.temp
Step 0 : COPY . /app/.temp
 ---> Using cache
Trigger 1, RUN /app/.cache/gotools/bin/compile
Step 0 : RUN /app/.cache/gotools/bin/compile
 ---> Using cache
 ---> 53f3c75d186c
root@3a1d7501c029:~/user/src/github.com/heroku-examples/go-websocket-chat-demo #

The shell container mounts your local working directory into the Docker container where it needs to be inside of a $GOPATH. Any changes that happen outside of that directory and it’s sub directories are however lost when the shell exits.

Wrap Up

Heroku’s Docker support is currently in beta. As we work to make the integration better, we’d love to hear your feedback so we can focus on building the things you need. Feel free to reach out to me directly with you thoughts and ideas.

You can visit the Heroku Dev Center for more information on Heroku’s Docker CLI. And you can learn more about Docker from their documentation site. You can also find more information about deploying Go apps to Heroku on the Dev Center.

comments powered by Disqus