Minimal Golang Heroku App

Heroku's documentation for its Golang buildpack isn't up to date, and it's unclear which files we really need to get started. I'm going to lay out the bare essentials for creating a minimal Golang Heroku app. I will explain the purpose of each and every file so we can assert that we do in fact have a bare-bones starter project for Go on Heroku.

History of the Heroku Go buildpack

Heroku supports many languages with its list of official buildpacks. One of the platform's more recent additions was the official Go buildpack.

Before there was an official buildpack for Golang, we could still create Go apps on Heroku using a custom buildpack created by a member of the community. Unfortunately, this was a more complicated option and had no official support from Heroku.

Since we can now use the official Heroku Go buildpack, we should take that approach. However, Heroku's Go buildpack documentation and coordinating demo app are still set up to use the old custom buildpack. For example, they still have a Dockerfile and Makefile. With the new buildpack, we don't need to manually interact with Docker or Make!

Heroku's Go demo app also has a /vendor folder for dependencies. The Go language has evolved and there is an official tool for managing dependencies: Go Modules. The official Heroku Go buildpack supports Go Modules, so we no longer need to use a 3rd party tool for vendoring.

Basically, Heroku's Go buildpack is far ahead of its documentation. This leads to a lot of dead code that we would be blindly copying if we started from Heroku's old example project. Let's cut that out and see how little it takes to get up and running on Heroku with Golang.

Setting up

You will of course need to install Go on your machine.

To follow along, you will also need to install the Heroku Toolbelt. This will allow you to use the Heroku CLI to set up and deploy our minimal Golang app.

Since the Heroku CLI uses Git as part of its deployment process, you will also need to have Git installed. Windows users can install Git Bash.

Create a Golang web server

Start by creating your project directory:

mkdir minimal-golang-heroku-app

Within that directory, create a main.go file:

package main
​
import (
    "fmt"
    "net/http"
)func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello World!")
    })
​
    http.ListenAndServe(":80", nil)
}

This is the minimum viable Go web server. All we are doing is listening for a request to the root path and responding with "Hello World!" If you run the code, you can open your browser to localhost to see the "Hello World!" response come back:

go run main.go

Set up a Git repository

We will deploy our project to Heroku by using Git, so let's initialize it as a Git repository and commit our changes.

Run these commands inside the project folder:

git init
git add .
git commit -m "Initial commit"

Create a Heroku app

Now that we have a web server, let's turn it into a Heroku app. We will set things up using the Heroku CLI while in the project directory:

heroku create

That command does two things. First, it creates an empty application on Heroku. It also creates an empty Git repository on Heroku to go along with the app. Finally, if your local project is a Git repository, this command will set the new Heroku repository as a remote named heroku.

You will see output like this:

$ heroku create
Creating app... done, ⬢ evening-ocean-54721
https://evening-ocean-54721.herokuapp.com/ | https://git.heroku.com/evening-ocean-54721.git

If you check the list of apps on your Heroku dashboard after running that command, you will see the new app has been created.

Push to Heroku

The only step left is to actually push the code from our computer up to our Heroku app so we can see it on the web. However, there is a catch.

Try pushing your app up to Heroku. Remember you need to set the upstream branch in Git since this is your first push:

git push -u heroku master

Heroku will reject your code at this point with these error messages:

No default language could be detected for this app.
HINT: This occurs when Heroku cannot detect the buildpack to use for this application automatically.
See https://devcenter.heroku.com/articles/buildpacks

In order to build and run your code, Heroku needs to know which buildpack to use. We can manually set the buildpack using the CLI, the GUI, or an app.json file. If we don't manually set a buildpack, Heroku will look for clues to determine which language to use, but it will stop if it can't figure it out.

I prefer for Heroku to intuit the correct buildpack, as it saves me a step (and potential troubleshooting) when deploying an app for the first time.

Initialize a Go module

Fortunately, it's easy to signal to Heroku that this is a Golang app. We just need to initialize it as its own Go module:

go mod init github.com/AaronSmithX/minimal-golang-heroku-app

This will create a go.mod file, which declares that the directory is a Go Module. Technically, you can name your module whatever you want. However, it's convention to name a module to match its Git repository.

Before we install any dependencies, our go.mod file is very minimal:

module github.com/AaronSmithX/minimal-golang-heroku-app
​
go 1.12

As you may have guessed, Heroku will look for a go.mod file in new projects to see if it should use the Go buildpack. Let's commit our go.mod file and try pushing to Heroku again (we still need to set our upstream branch since our last push was rejected):

git add go.mod
git commit -m "Added go.mod"
git push -u heroku master

This time, Heroku will accept our commit. It will successfully build our minimal Golang application and run it as a dyno.

Unfortunately, we have one more problem to solve. If you try to view your application online (you can find the URL in the git push output or on your Heroku dashboard), you'll see Heroku's "Application Error" screen instead of our "Hello World!" message.

Using Heroku environment variables

This is an easy fix. Since Heroku apps share a machine with other apps, they are assigned an arbitrary port to listen on. In other words, our app isn't going to be listening on port 80, which is what it tries to do by default. It also won't be listening to the same port each time it is started (and Heroku does love to restart its dynos).

To get the PORT environment variable from Heroku, update main.go:

package main
​
import (
    "fmt"
    "net/http"
    "os"
)func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello World!")
    })
​
    http.ListenAndServe(":"+os.Getenv("PORT"), nil)
}

We are importing the os package at the top, then using os.Getenv("PORT") to read the PORT environment variable. Notice we add a colon (:) in front of the port to get the correct syntax.

Let's test our app by passing in a PORT value:

PORT=8080 go run main.go

If you visit localhost:8080, you will see the app running.

Now make one final commit and push:

git add main.go
git commit -m "Use PORT"
git push

And with that, Heroku will accept our changes, successfully build our app, and finally deploy it to the web for all to see.

Final notes

You can see the complete example on my GitHub.

One thing we didn't do is install dependencies. Heroku doesn't require any extra steps here – if you know how to work with Go Modules, you already know how to manage dependencies on Heroku.