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.