Getting Started on Heroku Fir with Go
Introduction
Complete this tutorial to deploy a sample Go app to Heroku Private Spaces on the Fir generation of the platform. To deploy the app to the Common Runtime or Cedar Private Spaces, follow this guide instead.
The tutorial assumes that you have:
- A verified Heroku Account
- An existing Fir Private Space
- A team admin or member role that has the
app creation
permission on the space. - An SSH key added to your Heroku account
- Go 1.23 or better installed locally (Installation instructions available here)
- Postgres installed locally
Using dynos and databases to complete this tutorial counts towards your usage. We recommend using 1X-Classic dynos and an Essential-0 Postgres database to complete this tutorial. Delete all resources after completing the tutorial.
Set Up
The Heroku CLI requires Git, the popular version control system. If you don’t already have Git installed, complete the following before proceeding:
Install the Heroku Command Line Interface (CLI). Use the CLI to manage and scale your app, provision add-ons, view your logs, and run your app locally.
Download and run the installer for your platform:
$ brew tap heroku/brew && brew install heroku
Download the appropriate installer for your Windows installation:
You can find more installation options for the Heroku CLI here.
After installation, you can use the heroku
command from your command shell.
To log in to the Heroku CLI, use the heroku login
command:
$ heroku login
heroku: Press any key to open up the browser to login or q to exit:
Opening browser to https://cli-auth.heroku.com/auth/cli/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com
This command opens your web browser to the Heroku login page. If your browser is already logged in to Heroku, click the Log In
button on the page.
This authentication is required for the heroku
and git
commands to work correctly.
If you have any problems installing or using the Heroku CLI, see the main Heroku CLI article for advice and troubleshooting steps.
If you’re behind a firewall that uses a proxy to connect with external HTTP/HTTPS services, set the HTTP_PROXY
or HTTPS_PROXY
environment variables in your local development environment before running the heroku
command.
Clone the Sample App
If you’re new to Heroku, it’s recommended that you complete this tutorial using the Heroku-provided sample application.
To deploy an existing application, follow this article instead.
Clone the sample application to get a local version of the code. Execute these commands in your local command shell or terminal:
$ git clone https://github.com/heroku/go-getting-started.git
$ cd go-getting-started
You now have a functioning Git repository that contains a simple application. It includes a go.mod
file, which is used by Go and Go’s module dependency system.
Define a Procfile
Use a Procfile, a text file in the root directory of your application, to explicitly declare what command to execute to start your app.
The Procfile
in the example app looks like this:
web: go-getting-started
This Procfile declares a single process type, web
, and the command needed to run it. The name web
is important here. It declares that this process type is attached to Heroku’s HTTP routing stack and receives web traffic when deployed. The command used here, go-getting-started
is the compiled binary of the getting started app. The heroku build process makes this compiled binary available on the $PATH
.
A Procfile can contain additional process types. For example, you can declare a background worker process that processes items off a queue.
Create Your App in a Fir Space
Delete your app and database as soon as you’re done to control costs.
You can get a list of all Heroku spaces by running $ heroku spaces
Create an app on Heroku to prepare the platform to receive your source code by replacing <space-name>
with the name of your Fir space in the command below:
$ heroku create --space <space-name>
Creating app in space <space name>...
Creating app in space <space name>... done, quiet-cove-25289
http://quiet-cove-25289-64cbda77f01b.herokuapp.com/ | https://git.heroku.com/quiet-cove-25289.git
When you create an app, a Git remote called heroku
also gets created and associated with your local Git repository. Git remotes are versions of your repository that live on other servers. You deploy your app by pushing its code to that special Heroku-hosted remote associated with your app.
Heroku generates a random name for your app, in this case, quiet-cove-25289
. You can specify your own app name.
Deploy the App
Using a dyno to complete this tutorial counts towards your usage. Delete your app and database as soon as you’re done to control costs.
Deploy your code. This command pushes the main
branch of the sample repo to your heroku
remote, which then deploys to Heroku:
$ git push heroku main
remote: Updated 1299 paths from c6c7454
remote: Compressing source files... done.
remote: Building source:
remote: Waiting on build...
remote: Extracting source
remote: Image with name "quiet-cove-25289/builds" not found
remote: 2 of 3 buildpacks participating
remote: heroku/go 0.4.6
remote: heroku/procfile 3.1.2
remote:
remote: [Reading build configuration]
remote: Detected Go version requirement: =1.20
remote: Resolved Go version: go1.20.14 (linux-arm64)
remote:
remote: [Installing Go distribution]
remote: Installing go1.20.14 (linux-arm64) from https://go.dev/dl/go1.20.14.linux-arm64.tar.gz
remote:
remote: [Building Go binaries]
remote: Using vendored Go modules
remote: Creating Go build cache
remote: Resolving Go modules
remote: Building packages:
remote: - github.com/heroku/go-getting-started
remote:
remote: [Setting launch table]
remote: Detected processes:
remote: - go-getting-started: go-getting-started
remote: - web: go-getting-started
remote:
remote: [Discovering process types]
remote: Procfile declares types -> web
remote: Adding layer 'heroku/go:go_target'
remote: Adding layer 'buildpacksio/lifecycle:launch.sbom'
remote: Added 1/1 app layer(s)
remote: Adding layer 'buildpacksio/lifecycle:launcher'
remote: Adding layer 'buildpacksio/lifecycle:config'
remote: Adding layer 'buildpacksio/lifecycle:process-types'
remote: Adding label 'io.buildpacks.lifecycle.metadata'
remote: Adding label 'io.buildpacks.build.metadata'
remote: Adding label 'io.buildpacks.project.metadata'
remote: Setting default process type 'web'
remote: Saving quiet-cove-25289/builds...
remote: *** Images (sha256:b07c5762dc5c836718d307dc22c16dc3b9f4a58da562a7018307c4f6b435feab):
remote: quiet-cove-25289/builds:b2fd510b-cb47-4329-8696-7869e090a1c7
remote: Adding cache layer 'heroku/go:go_build'
remote: Adding cache layer 'heroku/go:go_dist'
remote: Uploading cache
remote: Launching...
remote: http://quiet-cove-25289-64cbda77f01b.herokuapp.com/ deployed to Heroku
remote: Verifying deploy... done.
To https://git.heroku.com/quiet-cove-25289.git
* [new branch] main -> main
The app is now deployed. The default dyno size for Fir Private Spaces is 1X-Classic.
Visit the app at the URL shown in the logs. As a shortcut, you can also open the website as follows:
$ heroku open
View Logs
Fir apps do not retain log history like Cedar apps. To view an event in your Fir logs, you must run the logging command while that event occurs.
Heroku treats logs as streams of time-ordered events, aggregated from the output streams of all your app and Heroku components. Heroku provides a single stream for all events. View information about your running app by using one of the logging commands:
$ heroku logs
Fetching logs...
2024-12-05T20:47:46.848702+00:00 heroku-router[web]: at=info method=GET path="/" host=quiet-cove-25289-64cbda77f01b.herokuapp.com request_id=ba28d158-b4df-6109-5499-ee8b229613ae fwd="13.110.54.11" dyno=web-6fcdbddb97-859bb connect=1ms service=1ms status=200 bytes=6819 protocol=http tls_version=tls1.3
2024-12-05T20:47:46.847741+00:00 app[web-6fcdbddb97-859bb]: [GIN] 2024/12/05 - 20:47:46 | 200 | 416.668µs | 13.110.54.11 | GET "/"
To generate more log messages, refresh the app in your browser.
To stop streaming the logs, press Ctrl
+C
.
Start a Console
The heroku run
command to launch an interactive one-off dyno is unavailable for Fir. As an alternative, use heroku run:inside
to access a running dyno until we add heroku run
for Fir.
You must add an SSH key to your Heroku account before running this command.
To execute the command you need the name of a currently running process. You can see a list with heroku ps
:
$ heroku ps
=== web (1X-Classic): go-getting-started (1)
web-6fcdbddb97-859bb: up 2024/12/05 14:47:36 -0600 (~ 13s ago)
Use that dyno name to run an interactive bash
session:
$ heroku run:inside web-6fcdbddb97-859bb "bash"
Running launcher bash on quiet-cove-25289...
Running launcher bash on quiet-cove-25289... up, web-6fcdbddb97-859bb
heroku@web-6fcdbddb97-859bb: /workspaceheroku@web-6fcdbddb97-859bb:/workspace$ ls -lah
total 68K
drwxrwsrwx. 6 heroku heroku 16K Jan 1 1980 .
drwxr-xr-x. 1 root root 17 Dec 5 20:47 ..
-rw-r--r--. 1 heroku heroku 10 Jan 1 1980 .env
-rw-r--r--. 1 heroku heroku 213 Jan 1 1980 .gitattributes
drwxr-xr-x. 2 heroku heroku 46 Jan 1 1980 .github
-rw-r--r--. 1 heroku heroku 24 Jan 1 1980 .gitignore
-rw-r--r--. 1 heroku heroku 545 Jan 1 1980 Dockerfile
-rw-r--r--. 1 heroku heroku 318 Jan 1 1980 Makefile
-rw-r--r--. 1 heroku heroku 24 Jan 1 1980 Procfile
-rw-r--r--. 1 heroku heroku 2.2K Jan 1 1980 README.md
-rw-r--r--. 1 heroku heroku 294 Jan 1 1980 app.json
-rw-r--r--. 1 heroku heroku 1.4K Jan 1 1980 go.mod
-rw-r--r--. 1 heroku heroku 7.4K Jan 1 1980 go.sum
-rw-r--r--. 1 heroku heroku 164 Jan 1 1980 heroku.yml
-rw-r--r--. 1 heroku heroku 466 Jan 1 1980 main.go
drwxr-xr-x. 2 heroku heroku 43 Jan 1 1980 static
drwxr-xr-x. 2 heroku heroku 74 Jan 1 1980 templates
drwxr-xr-x. 6 heroku heroku 102 Jan 1 1980 vendor
heroku@web-6fcdbddb97-859bb: /workspaceheroku@web-6fcdbddb97-859bb:/workspace$ exit
exit
If you receive an error, Error connecting to process
, configure your firewall.
Type exit
to exit the shell and terminate the dyno.
Declare App Dependencies
Heroku recognizes an app as being written in Go by the existence of a go.mod
file in the root directory. The demo app you deployed already has a go.mod
file, and it looks something like this:
module github.com/heroku/go-getting-started
// +heroku goVersion go1.20
go 1.20
require (
github.com/gin-gonic/gin v1.9.1
github.com/heroku/x v0.1.0
)
require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
The go.mod
file is used by Go tool and specifies both the dependencies that are required to build your application and the build configuration Heroku should use to compile the application. This Go app has a few dependencies, primarily on Gin, a HTTP web framework.
When an app is deployed, Heroku reads this file, installs an appropriate Go version and compiles your code using go install .
.
Run the app locally
Running apps locally in your own dev environment requires a little more effort. Go is a compiled language and you must compile the application and ensure it available on your $PATH
.
First, ensure you have $GOPATH/bin
on your path:
$ export GOPATH="$HOME/go"
$ export PATH="$GOPATH/bin:$PATH"
These environment variables are commonly used by Go developers. They may be something you want to persist to your environment by
including them your .zshrc
, .bash_profile
.
Then compile and install the program to your $GOPATH/bin
directory.
$ go install -v .
github.com/gin-gonic/gin/internal/bytesconv
github.com/go-playground/locales/currency
golang.org/x/net/html/atom
github.com/pelletier/go-toml/v2/internal/characters
google.golang.org/protobuf/internal/flags
google.golang.org/protobuf/internal/pragma
golang.org/x/text/internal/tag
golang.org/x/text/transform
golang.org/x/crypto/sha3
github.com/go-playground/locales
google.golang.org/protobuf/internal/detrand
github.com/gin-gonic/gin/internal/json
golang.org/x/net/html
github.com/gabriel-vasile/mimetype/internal/json
github.com/go-playground/universal-translator
github.com/leodido/go-urn
golang.org/x/text/internal/language
github.com/pelletier/go-toml/v2/internal/danger
google.golang.org/protobuf/internal/errors
golang.org/x/text/unicode/bidi
gopkg.in/yaml.v3
golang.org/x/sys/unix
google.golang.org/protobuf/encoding/protowire
golang.org/x/text/unicode/norm
github.com/pelletier/go-toml/v2/unstable
golang.org/x/net/http2/hpack
github.com/gin-contrib/sse
github.com/heroku/x/hmetrics
github.com/ugorji/go/codec
google.golang.org/protobuf/reflect/protoreflect
github.com/heroku/x/hmetrics/onload
github.com/pelletier/go-toml/v2/internal/tracker
golang.org/x/text/secure/bidirule
golang.org/x/text/internal/language/compact
github.com/pelletier/go-toml/v2
google.golang.org/protobuf/internal/encoding/messageset
google.golang.org/protobuf/internal/order
google.golang.org/protobuf/internal/genid
google.golang.org/protobuf/internal/strs
golang.org/x/text/language
google.golang.org/protobuf/runtime/protoiface
google.golang.org/protobuf/reflect/protoregistry
github.com/mattn/go-isatty
google.golang.org/protobuf/proto
github.com/gabriel-vasile/mimetype/internal/charset
golang.org/x/net/idna
github.com/gabriel-vasile/mimetype/internal/magic
golang.org/x/net/http/httpguts
github.com/gabriel-vasile/mimetype
golang.org/x/net/http2
github.com/go-playground/validator/v10
golang.org/x/net/http2/h2c
github.com/gin-gonic/gin/render
github.com/gin-gonic/gin/binding
github.com/gin-gonic/gin
github.com/heroku/go-getting-started
Now start your application locally using the heroku local
command, which was installed as part of the Heroku CLI:
$ heroku local web --port 5006
[OKAY] Loaded ENV .env File as KEY=VALUE Format
2:48:00 PM web.1 | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
2:48:00 PM web.1 | - using env: export GIN_MODE=release
2:48:00 PM web.1 | - using code: gin.SetMode(gin.ReleaseMode)
2:48:00 PM web.1 | [GIN-debug] Loaded HTML Templates (4):
2:48:00 PM web.1 | - header.tmpl.html
2:48:00 PM web.1 | - index.tmpl.html
2:48:00 PM web.1 | - nav.tmpl.html
2:48:00 PM web.1 | -
2:48:00 PM web.1 | [GIN-debug] GET /static/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (2 handlers)
2:48:00 PM web.1 | [GIN-debug] HEAD /static/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (2 handlers)
2:48:00 PM web.1 | [GIN-debug] GET / --> main.main.func1 (2 handlers)
2:48:00 PM web.1 | [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
2:48:00 PM web.1 | Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
2:48:00 PM web.1 | [GIN-debug] Listening and serving HTTP on :5006
On Windows you will need to do two things before being able to run `heroku local`:
- Run `go build -o bin/go-getting-started.exe -v` instead of the command listed above.
- Alter Procfile so it’s contents are: `web: bin\go-getting-started.exe` instead of what is in the checkout. Don’t commit changes to Procfile though, otherwise your application’s web process won’t be able to start on Heroku.
Just like Heroku, heroku local
examines the Procfile
to determine what to run.
Open http://localhost:5006 with your web browser. You should see your app running locally.
To stop the app from running locally, go back to your terminal window and press Ctrl
+C
to exit.
Provision a Database
The sample app requires a database. Provision a Heroku Postgres database, an add-on available through the Elements Marketplace. Add-ons are cloud services that provide out-of-the-box additional services for your application, such as logging, monitoring, databases, and more.
An essential-0
Postgres size costs $5 a month, prorated to the minute. At the end of this tutorial, we prompt you to delete your database to minimize costs.
$ heroku addons:create heroku-postgresql:essential-0
Creating heroku-postgresql:essential-0 on quiet-cove-25289...
Creating heroku-postgresql:essential-0 on quiet-cove-25289... ~$0.007/hour (max $5/month)
Database should be available soon
postgresql-shaped-25473 is being created in the background. The app will restart when complete...
Use heroku addons:info postgresql-shaped-25473 to check creation progress
Use heroku addons:docs heroku-postgresql to view documentation
You can wait for the database to provision by running this command:
$ heroku pg:wait
Waiting for database postgresql-shaped-25473... Provisioning
Waiting for database postgresql-shaped-25473... Available
After that command exits, your Heroku app can access the Postgres database. The DATABASE_URL
environment variable stores your credentials, which your app is configured to connect to. You can see all the add-ons provisioned with the addons
command:
$ heroku addons
Add-on Plan Price Max price State
─────────────────────────────────────────── ─────────── ──────────── ───────── ───────
heroku-postgresql (postgresql-shaped-25473) essential-0 ~$0.007/hour $5/month created
└─ as DATABASE
The table above shows add-ons and the attachments to the current app (quiet-cove-25289) or other apps.
Push Local Changes
In this step you’ll learn how to propagate a local change to the application through to Heroku. As an example, you’ll modify the application to add an additional dependency and the code to use it.
Dependencies are managed with the Go tool .
Let’s add a route to the application that will use the database we just created.
We’ll need the pq
library to interact with the database. Since this dependency is not already used by your application we need to tell go to fetch a copy of the dependency:
$ go get github.com/lib/pq@v1
go: downloading github.com/lib/pq v1.10.9
go: upgraded go 1.20 => 1.21
go: added toolchain go1.23.4
go: added github.com/lib/pq v1.10.9
This does 3 things:
- Downloads
v1
of the pg module and any of it’s dependencies to the module cache. - Records the pg dependency, and its dependencies in
go.mod
. - Records a cryptographic sum of pg and it’s dependencies in
go.sum
After that let’s introduce a new route, /db
, which will track timestamps of
requests to that endpoint. Modify main.go
so that it uses pf by adding "github.com/lib/pq@v1"
to the list of imports.
In file main.go
, on line 8 add:
_ "github.com/lib/pq"
Now let’s initialize a connection to the database which is defined by the
$DATABASE_URL
environment variable.
In file main.go
, on line 19 add:
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatalf("Error opening database: %q", err)
}
Now add a dbFunc
to the app.
At the end of main.go
add:
func dbFunc(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS ticks (tick timestamp)"); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error creating database table: %q", err))
return
}
if _, err := db.Exec("INSERT INTO ticks VALUES (now())"); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error incrementing tick: %q", err))
return
}
rows, err := db.Query("SELECT tick FROM ticks")
if err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error reading ticks: %q", err))
return
}
defer rows.Close()
for rows.Next() {
var tick time.Time
if err := rows.Scan(&tick); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error scanning ticks: %q", err))
return
}
c.String(http.StatusOK, fmt.Sprintf("Read from DB: %s\n", tick.String()))
}
}
}
Then register the new function with the router.
In file main.go
, on line 27 add:
router.GET("/db", dbFunc(db))
main.go
should now look like this:
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
_ "github.com/heroku/x/hmetrics/onload"
_ "github.com/lib/pq"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
log.Fatal("$PORT must be set")
}
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatalf("Error opening database: %q", err)
}
router := gin.New()
router.Use(gin.Logger())
router.GET("/db", dbFunc(db))
router.LoadHTMLGlob("templates/*.tmpl.html")
router.Static("/static", "static")
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl.html", nil)
})
router.Run(":" + port)
}
func dbFunc(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS ticks (tick timestamp)"); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error creating database table: %q", err))
return
}
if _, err := db.Exec("INSERT INTO ticks VALUES (now())"); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error incrementing tick: %q", err))
return
}
rows, err := db.Query("SELECT tick FROM ticks")
if err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error reading ticks: %q", err))
return
}
defer rows.Close()
for rows.Next() {
var tick time.Time
if err := rows.Scan(&tick); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error scanning ticks: %q", err))
return
}
c.String(http.StatusOK, fmt.Sprintf("Read from DB: %s\n", tick.String()))
}
}
}
Next, let’s add the new dependency (pq
) to the vendor folder. This will allow go install
to find the dependencies during builds both locally and during remote builds. Vendoring also ensures that builds are repeatable and resistant to erosion.
$ go mod vendor
go: downloading github.com/heroku/x v0.1.0
go: downloading github.com/gin-gonic/gin v1.9.1
go: downloading github.com/gin-contrib/sse v0.1.0
go: downloading github.com/mattn/go-isatty v0.0.19
go: downloading golang.org/x/net v0.23.0
go: downloading github.com/bytedance/sonic v1.9.1
go: downloading github.com/json-iterator/go v1.1.12
go: downloading github.com/go-playground/validator/v10 v10.14.0
go: downloading github.com/pelletier/go-toml/v2 v2.0.8
go: downloading github.com/ugorji/go/codec v1.2.11
go: downloading github.com/goccy/go-json v0.10.2
go: downloading google.golang.org/protobuf v1.33.0
go: downloading gopkg.in/yaml.v3 v3.0.1
go: downloading golang.org/x/sys v0.18.0
go: downloading github.com/modern-go/reflect2 v1.0.2
go: downloading github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
go: downloading github.com/gabriel-vasile/mimetype v1.4.2
go: downloading github.com/go-playground/universal-translator v0.18.1
go: downloading github.com/leodido/go-urn v1.2.4
go: downloading golang.org/x/crypto v0.21.0
go: downloading golang.org/x/text v0.14.0
go: downloading github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311
go: downloading golang.org/x/arch v0.3.0
go: downloading github.com/twitchyliquid64/golang-asm v0.15.1
go: downloading github.com/klauspost/cpuid/v2 v2.2.4
go: downloading github.com/go-playground/locales v0.14.1
Now test locally:
$ go install -v .
github.com/lib/pq/oid
database/sql/driver
os/user
github.com/lib/pq/scram
database/sql
github.com/lib/pq
github.com/heroku/go-getting-started
$ heroku local --port=5006
Visit your application at http://localhost:5006. If your changes worked, you see a log of timestamp. Refreshing the page should add more timestamps to the log.
Now deploy this local change to Heroku.
Almost every deploy to Heroku follows this same pattern. First, add the modified files to the local Git repository:
$ git add .
Now commit the changes to the repository:
$ git commit -m "Added db route"
[main e5169f0] Added db route
33 files changed, 7513 insertions(+), 1 deletion(-)
create mode 100644 vendor/github.com/lib/pq/.gitignore
create mode 100644 vendor/github.com/lib/pq/LICENSE.md
create mode 100644 vendor/github.com/lib/pq/README.md
create mode 100644 vendor/github.com/lib/pq/TESTS.md
create mode 100644 vendor/github.com/lib/pq/array.go
create mode 100644 vendor/github.com/lib/pq/buf.go
create mode 100644 vendor/github.com/lib/pq/conn.go
create mode 100644 vendor/github.com/lib/pq/conn_go115.go
create mode 100644 vendor/github.com/lib/pq/conn_go18.go
create mode 100644 vendor/github.com/lib/pq/connector.go
create mode 100644 vendor/github.com/lib/pq/copy.go
create mode 100644 vendor/github.com/lib/pq/doc.go
create mode 100644 vendor/github.com/lib/pq/encode.go
create mode 100644 vendor/github.com/lib/pq/error.go
create mode 100644 vendor/github.com/lib/pq/krb.go
create mode 100644 vendor/github.com/lib/pq/notice.go
create mode 100644 vendor/github.com/lib/pq/notify.go
create mode 100644 vendor/github.com/lib/pq/oid/doc.go
create mode 100644 vendor/github.com/lib/pq/oid/types.go
create mode 100644 vendor/github.com/lib/pq/rows.go
create mode 100644 vendor/github.com/lib/pq/scram/scram.go
create mode 100644 vendor/github.com/lib/pq/ssl.go
create mode 100644 vendor/github.com/lib/pq/ssl_permissions.go
create mode 100644 vendor/github.com/lib/pq/ssl_windows.go
create mode 100644 vendor/github.com/lib/pq/url.go
create mode 100644 vendor/github.com/lib/pq/user_other.go
create mode 100644 vendor/github.com/lib/pq/user_posix.go
create mode 100644 vendor/github.com/lib/pq/user_windows.go
create mode 100644 vendor/github.com/lib/pq/uuid.go
Now deploy as before:
$ git push heroku main
...
Finally, check that everything is working:
$ heroku open db
Define Config Vars
Heroku lets you externalize configuration, storing data such as encryption keys or external resource addresses in config vars.
At runtime, config vars are exposed as environment variables to the application. Your application is already reading one config var, the $PORT
config var. $PORT
is automatically set by Heroku on web
dynos. Let’s explore how to use user-set config vars in your Go application.
Modify main.go
and add a repeatHandler
function that returns Hello From Go!
the number of times specified by the value of the REPEAT
environment variable.
At the end of main.go
add:
func repeatHandler(r int) gin.HandlerFunc {
return func(c *gin.Context) {
var buffer bytes.Buffer
for i := 0; i < r; i++ {
buffer.WriteString("Hello from Go!\n")
}
c.String(http.StatusOK, buffer.String())
}
}
Next, let’s read the $REPEAT
environment variable.
In file main.go
, on line 27 add:
tStr := os.Getenv("REPEAT")
repeat, err := strconv.Atoi(tStr)
if err != nil {
log.Printf("Error converting $REPEAT to an int: %q - Using default\n", err)
repeat = 5
}
Now register the handler function with the router.
In file main.go
, on line 37 add:
router.GET("/repeat", repeatHandler(repeat))
The updated main.go
will look like this:
package main
import (
"bytes"
"database/sql"
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
"github.com/gin-gonic/gin"
_ "github.com/heroku/x/hmetrics/onload"
_ "github.com/lib/pq"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
log.Fatal("$PORT must be set")
}
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatalf("Error opening database: %q", err)
}
tStr := os.Getenv("REPEAT")
repeat, err := strconv.Atoi(tStr)
if err != nil {
log.Printf("Error converting $REPEAT to an int: %q - Using default\n", err)
repeat = 5
}
router := gin.New()
router.Use(gin.Logger())
router.GET("/repeat", repeatHandler(repeat))
router.GET("/db", dbFunc(db))
router.LoadHTMLGlob("templates/*.tmpl.html")
router.Static("/static", "static")
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl.html", nil)
})
router.Run(":" + port)
}
func dbFunc(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS ticks (tick timestamp)"); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error creating database table: %q", err))
return
}
if _, err := db.Exec("INSERT INTO ticks VALUES (now())"); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error incrementing tick: %q", err))
return
}
rows, err := db.Query("SELECT tick FROM ticks")
if err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error reading ticks: %q", err))
return
}
defer rows.Close()
for rows.Next() {
var tick time.Time
if err := rows.Scan(&tick); err != nil {
c.String(http.StatusInternalServerError,
fmt.Sprintf("Error scanning ticks: %q", err))
return
}
c.String(http.StatusOK, fmt.Sprintf("Read from DB: %s\n", tick.String()))
}
}
}
func repeatHandler(r int) gin.HandlerFunc {
return func(c *gin.Context) {
var buffer bytes.Buffer
for i := 0; i < r; i++ {
buffer.WriteString("Hello from Go!\n")
}
c.String(http.StatusOK, buffer.String())
}
}
The heroku local
command automatically sets up the environment based on the contents of the .env
file in your local directory. In the top level directory of your sample project, there’s already a .env
file that contains:
TIMES=10
Recompile the app and run it:
$ go install -v .
github.com/heroku/go-getting-started
$ heroku local --port=5006
When you access the /repeat
route on the app at http://localhost:5006/repeat you’ll see “Hello From Go!” ten times.
To set the config var on Heroku, execute the following:
$ heroku config:set TIMES=10
Setting TIMES and restarting quiet-cove-25289...
Setting TIMES and restarting quiet-cove-25289... done, v5
TIMES: 10
View the app’s config vars using heroku config
:
$ heroku config
=== quiet-cove-25289 Config Vars
DATABASE_URL: postgres://udlshmrd5i7vj2:pf298b5c4bb7f862863799e76008a75e26c4be0f87ce22396f501e1d60adfc993@cb5ajfjosdpmil.cluster-czrs8kj4isg7.us-east-1.rds.amazonaws.com:5432/d6see66qct6a2g
TIMES: 10
Deploy the changes to heroku using what you learned, and try it out by visiting the /repeat
handler of your application:
$ git add .
$ git commit -m "Added configurable repeat"
$ git push heroku main
$ heroku open repeat
Delete Your App
Remove the app from your account. We only charge you for the resources you used.
This action permanently deletes your application and any add-ons attached to it.
$ heroku apps:destroy
You can confirm that your app is gone with this command:
$ heroku apps --all
Next Steps
You now know how to configure and deploy a Go app, view logs, and start a console.
To learn more, see: