Building web applications can feel very rewarding, once you see some effects in the browser on your very own localhost. Unfortunately, it's harder from there on out, as you need to figure out how to share your project with the outside world. I've struggled often at this point as I was never sure if I should already share the project with the world, is it time to set up some complicated CI/CD workflow to push it to go live. I don't have these doubts any more ever since I saw how easy it is to deploy stuff to Heroku. Let me show you how you can do that, too.

What is Heroku

Heroku is a platform as a service platform that allows you to deploy code written in multiple languages, including Go. They have been around since 2007 when they started as Ruby-only platform. It offers a decently vast array of programs, including a free one, which does not guarantee your application will be blazingly fast, and your node will go to sleep after 30 minutes of inactivity. The first request after that will be slow since the app has to wake up. If you are OK with such limitations, and for the development/playground purposes we are, Heroku is definitely a solution worth checking out.

In this tutorial, I won't be covering setting up an account, so if you want to follow along, please sign up here.

Minimal Go app

For the purpose of this tutorial, we would need some simple web application in Go, so we will obviously go with a hello-world-ish HTTP server:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
        fmt.Fprint(rw, "This is mycodesmells/golang-examples server from Heroku!")
    })
    http.ListenAndServe(":5000", nil)
}

If you are at least a tiny bit familiar with the language, you will understand that this is an absolute minimum necessary to see Go in action on the web. To verify that it works, we run

# First terminal
$ go run main.go

# Second terminal
$ curl http://localhost:5000
This is mycodesmells/golang-examples server from Heroku!

Although the message we get is a lie at this point, we can see that the server works as expected.

Minimal Heroku configuration

Before we push anything to Heroku, we need to install a CLI client app which helps us with the whole workflow, as it automates the process and makes it look so easy. The installation is different depending on your OS, but it's described very well in the official documentation.

Once you have the client, you can create an app which represents your project in Heroku systems. You can do that by giving it a custom name, or you can rely on some random auto-generated one:

$ heroku create # use auto-generated name
$ heroku create mycodesmells-golang-examples

If the name is already taken, you will get an appropriate warning. Now we shift our focus to the code, and need to create a configuration file called heroku.yml:

# heroku.yml
build:
  docker:
    web: Dockerfile

This configuration basically tells us that we want to have a web target of type docker, which is described by Dockerfile. We could send the binary directly and trigger it using more complicated configuration (see an official example), but we will stick to using Docker. Why? This way we can isolate the building process from our local configuration so that even if I upgrade my local Go to version 1.12 beta, my Heroku builds will still rely on the stable versions (or the other way round if I feel like testing the latest versions in the cloud).

So, since we already promised Heroku that we have a Dockerfile, let's actually create it. We want to make sure the build is repeatable regardless of the local setup, so we are using a multi-step Docker image (check out my post on this for details) in which we start with an official Go build image and put the binary onto the image provided by Heroku:

### Build binary from official Go image
FROM golang:stretch as build
COPY . /app
WORKDIR /app
RUN go build -o /golang-examples .

### Put the binary onto Heroku image
FROM heroku/heroku:16
COPY --from=build /golang-examples /golang-examples
CMD ["/golang-examples"]

One last thing we need to do, is to make our applicationa accept PORT environmental variable in order to make it work in Heroku architecture (thanks ferrari for pointing this out, I forgot to cover this part). To do that, we change the line on which we expose the application to something like this:

...
import (
    "fmt"
    "net/http"
    "os"
)
...
http.ListenAndServe(":" + os.Getenv("PORT"), nil)

Heroku will choose some port for our application and pass it in, but we need to make sure that our container actually exposes something on that very port.

We have everything code-wise in place, we need to push the image onto Heroku servers with container:push subcommand:

$ heroku container:push -a mycodesmells-golang-examples web
...
Your image has been successfully pushed. You can now release it with the 'container:release' command

Pushing the changes is not enough to see them in action as you can see, so we follow the advice from the output above, and call container:release now:

$ heroku container:release -a mycodesmells-golang-examples web
Releasing images web to mycodesmells-golang-examples... done

It's finally time to check if your application is accessible on the web:

$ curl https://mycodesmells-golang-examples.herokuapp.com
This is mycodesmells/golang-examples server from Heroku!

It is! A huge success in just a couple of minutes!

Summary

It might not be the freshest of the news out there, but I am really impressed and happy how little effort is needed to go from typing a few lines of code to seeing them in action on the web. Once you already have a Heroku account, and the base Docker images are already downloaded to the machine, it really takes just a few minutes to publish your work to the whole world, which is just awesome!

Versions

  • Go: go1.11.5 darwin/amd64
  • Docker client: 18.09.0
  • Heroku CLI: heroku/7.19.4 darwin-x64 node-v11.3.0