Skip Navigation
Show nav
Heroku Dev Center Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
Heroku Dev Center Dev Center
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
    • .NET
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log in or Sign up

Getting Started on Heroku with Go

Introduction

Complete this tutorial to deploy a sample Go app to Cedar, the legacy generation of the Heroku platform. To deploy the app to the Fir generation, only available to Heroku Private Spaces, follow this guide instead.

The tutorial assumes that you have:

  • A verified Heroku Account
  • An Eco dynos plan subscription (recommended)
  • 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 our low-cost plans to complete this tutorial. Eligible students can apply for platform credits through our new Heroku for GitHub Students program.

Set Up

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.

The Heroku CLI requires Git, the popular version control system. If you don’t already have Git installed, complete the following before proceeding:

  • Git installation
  • First-time Git setup

Download and run the installer for your platform:

apple logomacOS

Install Homebrew and run:

$ brew install heroku/brew/heroku

windows logoWindows

Download the appropriate installer for your Windows installation:

64-bit installer

32-bit installer

You can find more installation options for the Heroku CLI here.

After installation, you can use the heroku command from your command shell.

On Windows, start the Command Prompt (cmd.exe) or Powershell to access the 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.

Create Your App

Using a dyno and a database to complete this tutorial counts towards your usage. Delete your app, and database as soon as you’re done to control costs.

 

Apps use Eco dynos if you’re subscribed to Eco by default. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account. It’s recommended if you plan on deploying many small apps to Heroku. Learn more here. Eligible students can apply for platform credits through our Heroku for GitHub Students program.

Create an app on Heroku to prepare the platform to receive your source code:

$ heroku create --stack heroku-26
Creating app... done, enigmatic-caverns-43977, stack is heroku-26
https://enigmatic-caverns-43977-efb09ce0b3eb.herokuapp.com/ | https://git.heroku.com/enigmatic-caverns-43977.git

When you create an app, a Git remote named heroku is also created and added to your local repository configuration. Git remotes are versions of your repository that live on other servers. You can deploy your app by pushing code to that special Heroku-hosted remote associated with your app.

Heroku generates a random name for your app, in this case, enigmatic-caverns-43977. 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 19 paths from 02c2453
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-26 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Go app detected
remote: -----> Detected go modules via go.mod
remote: -----> Detected Module Name: github.com/heroku/go-getting-started
remote: -----> New Go Version, clearing old cache
remote: -----> Installing go1.25.10
remote: -----> Fetching go1.25.10.linux-amd64.tar.gz
remote: -----> Determining packages to install
remote:        go: downloading github.com/gin-gonic/gin v1.12.0
remote:        go: downloading github.com/heroku/x v0.6.0
remote:        go: downloading github.com/gin-contrib/sse v1.1.0
remote:        go: downloading github.com/mattn/go-isatty v0.0.20
remote:        go: downloading github.com/quic-go/quic-go v0.59.0
remote:        go: downloading golang.org/x/net v0.53.0
remote:        go: downloading github.com/go-playground/validator/v10 v10.30.1
remote:        go: downloading github.com/goccy/go-yaml v1.19.2
remote:        go: downloading github.com/pelletier/go-toml/v2 v2.2.4
remote:        go: downloading github.com/ugorji/go/codec v1.3.1
remote:        go: downloading go.mongodb.org/mongo-driver/v2 v2.5.0
remote:        go: downloading google.golang.org/protobuf v1.36.11
remote:        go: downloading golang.org/x/sys v0.43.0
remote:        go: downloading github.com/quic-go/qpack v0.6.0
remote:        go: downloading github.com/gabriel-vasile/mimetype v1.4.12
remote:        go: downloading github.com/go-playground/universal-translator v0.18.1
remote:        go: downloading github.com/leodido/go-urn v1.4.0
remote:        go: downloading golang.org/x/crypto v0.50.0
remote:        go: downloading golang.org/x/text v0.36.0
remote:        go: downloading github.com/go-playground/locales v0.14.1
remote: -----> Detected the following main packages to install:
remote:        github.com/heroku/go-getting-started
remote: -----> Running: go install -v -tags heroku  github.com/heroku/go-getting-started
remote:        internal/unsafeheader
remote:        internal/goarch
remote:        internal/byteorder
remote:        internal/cpu
remote:        internal/coverage/rtcov
remote:        internal/abi
remote:        internal/godebugs
remote:        internal/goexperiment
remote:        internal/goos
remote:        internal/profilerecord
remote:        internal/bytealg
remote:        internal/chacha8rand
remote:        internal/runtime/atomic
remote:        internal/runtime/math
...
remote:        golang.org/x/crypto/hkdf
remote:        github.com/quic-go/quic-go/internal/utils/linkedlist
remote:        golang.org/x/net/bpf
remote:        golang.org/x/net/internal/socket
remote:        golang.org/x/text/transform
remote:        github.com/quic-go/quic-go/qlog
remote:        golang.org/x/text/unicode/bidi
remote:        golang.org/x/net/ipv4
remote:        golang.org/x/net/ipv6
remote:        github.com/quic-go/quic-go/internal/congestion
remote:        github.com/quic-go/quic-go/internal/handshake
remote:        github.com/quic-go/quic-go/internal/ackhandler
remote:        golang.org/x/text/secure/bidirule
remote:        log/slog/internal/buffer
remote:        golang.org/x/text/unicode/norm
remote:        log/slog
remote:        github.com/quic-go/quic-go
remote:        golang.org/x/net/internal/httpsfv
remote:        golang.org/x/net/idna
remote:        golang.org/x/net/http/httpguts
remote:        golang.org/x/net/internal/httpcommon
remote:        github.com/gin-gonic/gin/internal/fs
remote:        github.com/gin-contrib/sse
remote:        net/rpc
remote:        golang.org/x/net/http2
remote:        net/http/httputil
remote:        github.com/heroku/x/hmetrics
remote:        github.com/heroku/x/hmetrics/onload
remote:        github.com/ugorji/go/codec
remote:        github.com/quic-go/quic-go/http3/qlog
remote:        github.com/quic-go/quic-go/http3
remote:        golang.org/x/net/http2/h2c
remote:        github.com/gin-gonic/gin/render
remote:        github.com/gin-gonic/gin/binding
remote:        github.com/gin-gonic/gin
remote:        github.com/heroku/go-getting-started
remote: -----> Installed the following binaries:
remote:        ./bin/go-getting-started
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing...
remote:        Done: 14.8M
remote: -----> Launching...
remote:        Released v3
remote:        https://enigmatic-caverns-43977-efb09ce0b3eb.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/enigmatic-caverns-43977.git
 * [new branch]      main -> main

Visit the app at the URL shown in the logs. As a shortcut, you can also open the website as follows:

$ heroku open

Understanding the 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.

View Logs

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 --tail:

$ heroku logs --tail
2026-06-02T20:34:02.884573+00:00 app[api]: Initial release by user developer@example.com
2026-06-02T20:34:02.884573+00:00 app[api]: Release v1 created by user developer@example.com
2026-06-02T20:34:03.034654+00:00 app[api]: Release v2 created by user developer@example.com
2026-06-02T20:34:03.034654+00:00 app[api]: Enable Logplex by user developer@example.com
2026-06-02T20:34:20.000000+00:00 app[api]: Build started by user developer@example.com
2026-06-02T20:34:59.631792+00:00 app[api]: Deploy b32f38b2 by user developer@example.com
2026-06-02T20:34:59.631792+00:00 app[api]: Release v3 created by user developer@example.com
2026-06-02T20:34:59.653635+00:00 app[api]: Scaled to web@1:Basic by user developer@example.com
2026-06-02T20:35:00.422598+00:00 heroku[web.1]: Starting process with command `go-getting-started`
2026-06-02T20:35:01.233438+00:00 app[web.1]: [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
2026-06-02T20:35:01.233459+00:00 app[web.1]: - using env:	export GIN_MODE=release
2026-06-02T20:35:01.233459+00:00 app[web.1]: - using code:	gin.SetMode(gin.ReleaseMode)
2026-06-02T20:35:01.233459+00:00 app[web.1]:
2026-06-02T20:35:01.233647+00:00 app[web.1]: [GIN-debug] Loaded HTML Templates (4):
2026-06-02T20:35:01.233647+00:00 app[web.1]: -

To generate more log messages, refresh the app in your browser.

To stop streaming the logs, press Control+C.

Scale the App

After deploying the sample app, it automatically runs on a single web dyno. Think of a dyno as a lightweight container that runs the command specified in the Procfile.

You can check how many dynos are running by using the ps command:

$ heroku ps
=== web (Basic): go-getting-started (1)

web.1: up 2026/06/02 20:35:01 +0000 (~ 21s ago)

Scaling an app on Heroku is equivalent to changing the number of running dynos. Scale the number of web dynos to zero:

$ heroku ps:scale web=0
$ heroku ps:wait

Access the app again by hitting refresh in your browser, or heroku open to open it in a web tab. You get an error message because you no longer have web dynos available to serve requests.

Scale it up again:

$ heroku ps:scale web=1
$ heroku ps:wait

By default, apps use Eco dynos if you’re subscribed to Eco. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Eco dynos sleep if they don’t receive any traffic for half an hour. This sleep behavior causes a few seconds delay for the first request upon waking. Eco dynos consume from a monthly, account-level quota of eco dyno hours. As long as you haven’t exhausted the quota, your apps can continue to run.

To avoid dyno sleeping, upgrade to a Basic or higher dyno type as described in the Dyno Types article. Upgrading to at least Standard dynos allows you to scale up to multiple dynos per process type.

Start a Console

You can run a command, typically scripts and applications that are part of your app, in a one-off dyno using the heroku run command. You can also run an interactive bash session in your app’s environment:

$ heroku run bash
Running bash on enigmatic-caverns-43977... starting, run.7097
Running bash on enigmatic-caverns-43977... connecting, run.7097
Running bash on enigmatic-caverns-43977... up, run.7097
~ $ ls -lah
total 84K
drwx------  8 u18607 dyno 4.0K Jun  2 20:35 .
drwxr-xr-x 11 root   root 4.0K May 19 11:55 ..
-rw-------  1 u18607 dyno   86 Jun  2 20:34 .env
-rw-------  1 u18607 dyno  213 Jun  2 20:34 .gitattributes
drwx------  2 u18607 dyno 4.0K Jun  2 20:34 .github
-rw-------  1 u18607 dyno   43 Jun  2 20:34 .gitignore
drwx------  3 u18607 dyno 4.0K Jun  2 20:34 .heroku
drwx------  2 u18607 dyno 4.0K Jun  2 20:34 .profile.d
-rw-------  1 u18607 dyno  545 Jun  2 20:34 Dockerfile
-rw-------  1 u18607 dyno  318 Jun  2 20:34 Makefile
-rw-------  1 u18607 dyno   24 Jun  2 20:34 Procfile
-rw-------  1 u18607 dyno 2.1K Jun  2 20:34 README.md
-rw-------  1 u18607 dyno  294 Jun  2 20:34 app.json
drwx------  2 u18607 dyno 4.0K Jun  2 20:34 bin
-rw-------  1 u18607 dyno 1.6K Jun  2 20:34 go.mod
-rw-------  1 u18607 dyno 7.9K Jun  2 20:34 go.sum
-rw-------  1 u18607 dyno  164 Jun  2 20:34 heroku.yml
-rw-------  1 u18607 dyno  466 Jun  2 20:34 main.go
drwx------  2 u18607 dyno 4.0K Jun  2 20:34 static
drwx------  2 u18607 dyno 4.0K Jun  2 20:34 templates
~ $                                                                            
~ $ exit

If you receive an error, Error connecting to process, configure your firewall.

Type exit to exit the shell.

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.25
go 1.25.0

require (
	github.com/gin-gonic/gin v1.12.0
	github.com/heroku/x v0.6.0
)

require (
	github.com/bytedance/gopkg v0.1.3 // indirect
	github.com/bytedance/sonic v1.15.0 // indirect
	github.com/bytedance/sonic/loader v0.5.0 // indirect
	github.com/cloudwego/base64x v0.1.6 // indirect
	github.com/gabriel-vasile/mimetype v1.4.12 // indirect
	github.com/gin-contrib/sse v1.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.30.1 // indirect
	github.com/goccy/go-json v0.10.5 // indirect
	github.com/goccy/go-yaml v1.19.2 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/leodido/go-urn v1.4.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // 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.2.4 // indirect
	github.com/quic-go/qpack v0.6.0 // indirect
	github.com/quic-go/quic-go v0.59.0 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/ugorji/go/codec v1.3.1 // indirect
	go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
	golang.org/x/arch v0.22.0 // indirect
	golang.org/x/crypto v0.50.0 // indirect
	golang.org/x/net v0.53.0 // indirect
	golang.org/x/sys v0.43.0 // indirect
	golang.org/x/text v0.36.0 // indirect
	google.golang.org/protobuf v1.36.11 // 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 .
go: downloading github.com/gin-gonic/gin v1.12.0
go: downloading github.com/heroku/x v0.6.0
go: downloading github.com/gin-contrib/sse v1.1.0
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading github.com/quic-go/quic-go v0.59.0
go: downloading golang.org/x/net v0.53.0
go: downloading github.com/go-playground/validator/v10 v10.30.1
go: downloading github.com/goccy/go-yaml v1.19.2
go: downloading github.com/pelletier/go-toml/v2 v2.2.4
go: downloading github.com/ugorji/go/codec v1.3.1
go: downloading go.mongodb.org/mongo-driver/v2 v2.5.0
go: downloading google.golang.org/protobuf v1.36.11
go: downloading golang.org/x/sys v0.43.0
go: downloading github.com/gabriel-vasile/mimetype v1.4.12
go: downloading github.com/go-playground/universal-translator v0.18.1
go: downloading github.com/leodido/go-urn v1.4.0
go: downloading golang.org/x/crypto v0.50.0
go: downloading golang.org/x/text v0.36.0
go: downloading github.com/quic-go/qpack v0.6.0
go: downloading github.com/go-playground/locales v0.14.1
internal/goarch
internal/unsafeheader
internal/byteorder
internal/cpu
internal/coverage/rtcov
internal/abi
internal/godebugs
internal/goexperiment
internal/goos
internal/profilerecord
internal/runtime/atomic
internal/runtime/math
internal/bytealg
internal/chacha8rand
internal/runtime/strconv
internal/runtime/syscall
internal/runtime/exithook
internal/runtime/gc
internal/asan
internal/msan
internal/runtime/sys
internal/runtime/cgroup
internal/stringslite
internal/race
internal/trace/tracev2
sync/atomic
internal/runtime/maps
internal/synctest
math/bits
unicode
unicode/utf8
internal/sync
cmp
crypto/internal/fips140/alias
crypto/internal/fips140deps/byteorder
math
crypto/internal/fips140/subtle
encoding
internal/itoa
unicode/utf16
container/list
runtime
crypto/internal/fips140deps/cpu
crypto/internal/boring/sig
vendor/golang.org/x/crypto/cryptobyte/asn1
vendor/golang.org/x/crypto/internal/alias
internal/nettrace
log/internal
github.com/gin-gonic/gin/internal/bytesconv
github.com/go-playground/locales/currency
github.com/leodido/go-urn/scim/schema
github.com/pelletier/go-toml/v2/internal/characters
go.mongodb.org/mongo-driver/v2/internal/bsoncoreutil
google.golang.org/protobuf/internal/flags
golang.org/x/crypto/internal/alias
github.com/quic-go/quic-go/internal/utils/ringbuffer
golang.org/x/net/internal/iana
log/slog/internal
iter
internal/reflectlite
crypto/subtle
sync
slices
weak
maps
errors
sort
internal/oserror
io
strconv
path
internal/bisect
bytes
strings
internal/godebug
encoding/base64
reflect
syscall
bufio
internal/testlog
hash
hash/crc32
crypto
crypto/internal/fips140deps/godebug
crypto/internal/fips140
crypto/internal/impl
crypto/internal/fips140/sha3
crypto/internal/fips140/sha256
crypto/internal/fips140/sha512
math/rand/v2
crypto/internal/fips140/hmac
crypto/internal/randutil
crypto/internal/fips140/check
math/rand
crypto/internal/fips140/aes
crypto/internal/fips140/nistec/fiat
time
internal/syscall/unix
internal/syscall/execenv
crypto/internal/fips140/edwards25519/field
crypto/internal/fips140/bigmod
crypto/internal/fips140cache
crypto/sha3
crypto/internal/fips140hash
internal/saferio
crypto/internal/fips140/edwards25519
crypto/internal/fips140/nistec
crypto/internal/fips140/hkdf
io/fs
internal/poll
internal/fmtsort
internal/filepathlite
encoding/binary
context
crypto/internal/fips140/tls12
crypto/internal/fips140/tls13
os
crypto/fips140
crypto/tls/internal/fips140tls
encoding/pem
vendor/golang.org/x/net/dns/dnsmessage
vendor/golang.org/x/crypto/internal/poly1305
internal/singleflight
unique
runtime/cgo
vendor/golang.org/x/text/transform
net/netip
net/http/internal/ascii
github.com/gabriel-vasile/mimetype/internal/scan
fmt
crypto/internal/sysrand
crypto/internal/entropy
crypto/internal/fips140/drbg
crypto/internal/fips140/aes/gcm
crypto/internal/fips140only
crypto/cipher
crypto/internal/fips140/ecdh
encoding/xml
flag
encoding/json
crypto/internal/boring
compress/flate
math/big
crypto/aes
crypto/des
crypto/ecdh
crypto/internal/fips140/ecdsa
compress/gzip
crypto/sha512
crypto/internal/fips140/ed25519
crypto/hkdf
crypto/hmac
crypto/internal/fips140/mlkem
vendor/golang.org/x/crypto/chacha20
vendor/golang.org/x/sys/cpu
crypto/md5
crypto/rc4
vendor/golang.org/x/crypto/chacha20poly1305
crypto/internal/fips140/rsa
crypto/sha1
crypto/rand
crypto/elliptic
crypto/internal/boring/bbig
encoding/asn1
crypto/ed25519
crypto/internal/hpke
crypto/rsa
crypto/sha256
crypto/dsa
encoding/hex
net/url
vendor/golang.org/x/crypto/cryptobyte
crypto/x509/pkix
path/filepath
log
crypto/ecdsa
vendor/golang.org/x/text/unicode/norm
vendor/golang.org/x/text/unicode/bidi
vendor/golang.org/x/text/secure/bidirule
vendor/golang.org/x/net/http2/hpack
mime
vendor/golang.org/x/net/idna
mime/quotedprintable
net/http/internal
github.com/gin-gonic/gin/codec/json
github.com/gabriel-vasile/mimetype/internal/markup
hash/adler32
compress/zlib
github.com/gabriel-vasile/mimetype/internal/charset
debug/dwarf
github.com/gabriel-vasile/mimetype/internal/csv
github.com/gabriel-vasile/mimetype/internal/json
github.com/go-playground/locales
github.com/go-playground/universal-translator
github.com/leodido/go-urn
golang.org/x/sys/cpu
golang.org/x/crypto/sha3
golang.org/x/text/internal/tag
debug/macho
golang.org/x/text/internal/language
regexp/syntax
github.com/gabriel-vasile/mimetype/internal/magic
golang.org/x/text/internal/language/compact
regexp
github.com/gabriel-vasile/mimetype
golang.org/x/text/language
github.com/goccy/go-yaml/token
github.com/pelletier/go-toml/v2/internal/danger
github.com/pelletier/go-toml/v2/unstable
github.com/goccy/go-yaml/ast
github.com/goccy/go-yaml/scanner
github.com/pelletier/go-toml/v2/internal/tracker
github.com/pelletier/go-toml/v2
github.com/goccy/go-yaml/printer
github.com/goccy/go-yaml/internal/format
github.com/goccy/go-yaml/internal/errors
github.com/goccy/go-yaml/lexer
encoding/base32
github.com/goccy/go-yaml/parser
encoding/gob
go/token
html
text/template/parse
github.com/goccy/go-yaml
text/template
net
runtime/debug
html/template
go.mongodb.org/mongo-driver/v2/internal/decimal128
go.mongodb.org/mongo-driver/v2/internal/binaryutil
go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore
hash/fnv
google.golang.org/protobuf/internal/detrand
google.golang.org/protobuf/internal/errors
google.golang.org/protobuf/encoding/protowire
google.golang.org/protobuf/internal/pragma
google.golang.org/protobuf/reflect/protoreflect
golang.org/x/sys/unix
go.mongodb.org/mongo-driver/v2/bson
google.golang.org/protobuf/internal/encoding/messageset
google.golang.org/protobuf/internal/genid
google.golang.org/protobuf/internal/order
google.golang.org/protobuf/internal/strs
google.golang.org/protobuf/reflect/protoregistry
google.golang.org/protobuf/runtime/protoiface
google.golang.org/protobuf/proto
golang.org/x/net/http2/hpack
github.com/mattn/go-isatty
github.com/quic-go/quic-go/internal/monotime
github.com/quic-go/quic-go/quicvarint
github.com/quic-go/quic-go/qlogwriter/jsontext
golang.org/x/crypto/chacha20
github.com/quic-go/qpack
golang.org/x/crypto/internal/poly1305
golang.org/x/crypto/hkdf
golang.org/x/crypto/chacha20poly1305
github.com/quic-go/quic-go/internal/utils/linkedlist
golang.org/x/net/bpf
golang.org/x/text/transform
golang.org/x/text/unicode/bidi
golang.org/x/text/unicode/norm
log/slog/internal/buffer
golang.org/x/text/secure/bidirule
log/slog
golang.org/x/net/internal/httpsfv
golang.org/x/net/idna
vendor/golang.org/x/net/http/httpproxy
crypto/x509
golang.org/x/net/internal/socket
net/textproto
vendor/golang.org/x/net/http/httpguts
mime/multipart
net/mail
golang.org/x/net/ipv4
golang.org/x/net/ipv6
github.com/go-playground/validator/v10
golang.org/x/net/http/httpguts
crypto/tls
net/http/httptrace
github.com/quic-go/quic-go/internal/protocol
net/http/internal/httpcommon
golang.org/x/net/internal/httpcommon
github.com/quic-go/quic-go/internal/utils
github.com/quic-go/quic-go/internal/qerr
github.com/quic-go/quic-go/qlogwriter
github.com/quic-go/quic-go/internal/flowcontrol
github.com/quic-go/quic-go/internal/wire
net/http
github.com/quic-go/quic-go/qlog
github.com/quic-go/quic-go/internal/congestion
github.com/quic-go/quic-go/internal/handshake
github.com/quic-go/quic-go/internal/ackhandler
github.com/quic-go/quic-go
github.com/quic-go/quic-go/http3/qlog
github.com/gin-contrib/sse
github.com/gin-gonic/gin/internal/fs
net/rpc
github.com/quic-go/quic-go/http3
golang.org/x/net/http2
net/http/httputil
github.com/heroku/x/hmetrics
github.com/ugorji/go/codec
github.com/heroku/x/hmetrics/onload
golang.org/x/net/http2/h2c
github.com/gin-gonic/gin/binding
github.com/gin-gonic/gin/render
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
(node:15822) [DEP0060] DeprecationWarning: The `util._extend` API is deprecated. Please use Object.assign() instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
8:36:14 PM web.1 |  [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
8:36:14 PM web.1 |   - using env:	export GIN_MODE=release
8:36:14 PM web.1 |   - using code:	gin.SetMode(gin.ReleaseMode)
8:36:14 PM web.1 |  [GIN-debug] Loaded HTML Templates (4):
8:36:14 PM web.1 |  	- index.tmpl.html
8:36:14 PM web.1 |  	- nav.tmpl.html
8:36:14 PM web.1 |  	-
8:36:14 PM web.1 |  	- header.tmpl.html
8:36:14 PM web.1 |  [GIN-debug] GET    /static/*filepath         --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (2 handlers)
8:36:14 PM web.1 |  [GIN-debug] HEAD   /static/*filepath         --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (2 handlers)
8:36:14 PM web.1 |  [GIN-debug] GET    /                         --> main.main.func1 (2 handlers)
8:36:14 PM web.1 |  [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
8:36:14 PM web.1 |  Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
8:36:14 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`:

  1. Run `go build -o bin/go-getting-started.exe -v` instead of the command listed above.
  2. 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 enigmatic-caverns-43977... ~$0.007/hour (max $5/month)
Database should be available soon
postgresql-contoured-48546 is being created in the background. The app will restart when complete...
Run heroku addons:info postgresql-contoured-48546 to check creation progress.
Run heroku addons:docs heroku-postgresql to view documentation.

You can wait for the database to provision by running this command:

$ heroku pg:wait

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-contoured-48546)  essential-0  ~$0.007/hour  $5/month   created
 └─ as DATABASE


The table above shows add-ons and the attachments to the current app (enigmatic-caverns-43977) 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.12.3
go: added github.com/lib/pq v1.12.3

This does 3 things:

  1. Downloads v1 of the pg module and any of it’s dependencies to the module cache.
  2. Records the pg dependency, and its dependencies in go.mod.
  3. 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))

The additional code requires new packages from Go’s standard library. You can import them automatically with goimports. Install goimports if you haven’t already with:

$ go install golang.org/x/tools/cmd/goimports@latest
go: downloading golang.org/x/tools v0.45.0
go: downloading golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6
go: downloading golang.org/x/mod v0.36.0
go: downloading golang.org/x/sync v0.20.0

Then add the new imports to main.go:

$ goimports -w main.go

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/bytedance/sonic v1.15.0
go: downloading github.com/goccy/go-json v0.10.5
go: downloading github.com/json-iterator/go v1.1.12
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/bytedance/gopkg v0.1.3
go: downloading github.com/klauspost/cpuid/v2 v2.3.0
go: downloading github.com/twitchyliquid64/golang-asm v0.15.1
go: downloading github.com/bytedance/sonic/loader v0.5.0
go: downloading github.com/cloudwego/base64x v0.1.6
go: downloading golang.org/x/arch v0.22.0

In order to test the new code locally, we’ll want a local postgres database. If you don’t have postgres installed locally, follow these instructions. Then create a database for use with our app:

$ createdb go-getting-started

Now test the local changes using the local database:

$ go install -v .
github.com/gin-gonic/gin/internal/bytesconv
github.com/go-playground/locales/currency
github.com/leodido/go-urn/scim/schema
github.com/pelletier/go-toml/v2/internal/characters
database/sql/driver
github.com/gin-gonic/gin/codec/json
github.com/gabriel-vasile/mimetype/internal/scan
github.com/gin-contrib/sse
github.com/gabriel-vasile/mimetype/internal/json
github.com/gabriel-vasile/mimetype/internal/markup
github.com/gabriel-vasile/mimetype/internal/charset
github.com/gabriel-vasile/mimetype/internal/csv
github.com/go-playground/locales
github.com/leodido/go-urn
github.com/gabriel-vasile/mimetype/internal/magic
database/sql
github.com/go-playground/universal-translator
golang.org/x/sys/cpu
golang.org/x/crypto/sha3
golang.org/x/text/internal/tag
github.com/gabriel-vasile/mimetype
golang.org/x/text/internal/language
github.com/goccy/go-yaml/token
github.com/pelletier/go-toml/v2/internal/danger
github.com/pelletier/go-toml/v2/unstable
github.com/goccy/go-yaml/ast
github.com/goccy/go-yaml/scanner
github.com/pelletier/go-toml/v2/internal/tracker
golang.org/x/text/internal/language/compact
github.com/pelletier/go-toml/v2
golang.org/x/text/language
github.com/goccy/go-yaml/printer
github.com/goccy/go-yaml/internal/format
github.com/goccy/go-yaml/internal/errors
github.com/go-playground/validator/v10
github.com/goccy/go-yaml/lexer
github.com/goccy/go-yaml/parser
github.com/ugorji/go/codec
go.mongodb.org/mongo-driver/v2/internal/decimal128
go.mongodb.org/mongo-driver/v2/internal/binaryutil
go.mongodb.org/mongo-driver/v2/internal/bsoncoreutil
go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore
github.com/goccy/go-yaml
go.mongodb.org/mongo-driver/v2/bson
google.golang.org/protobuf/internal/detrand
google.golang.org/protobuf/internal/errors
google.golang.org/protobuf/encoding/protowire
google.golang.org/protobuf/internal/pragma
google.golang.org/protobuf/reflect/protoreflect
google.golang.org/protobuf/internal/flags
github.com/gin-gonic/gin/internal/fs
golang.org/x/sys/unix
google.golang.org/protobuf/internal/encoding/messageset
google.golang.org/protobuf/internal/genid
google.golang.org/protobuf/internal/order
google.golang.org/protobuf/internal/strs
google.golang.org/protobuf/reflect/protoregistry
google.golang.org/protobuf/runtime/protoiface
google.golang.org/protobuf/proto
golang.org/x/net/http2/hpack
github.com/mattn/go-isatty
github.com/quic-go/quic-go/internal/monotime
github.com/quic-go/quic-go/quicvarint
github.com/quic-go/quic-go/internal/protocol
github.com/quic-go/qpack
github.com/quic-go/quic-go/qlogwriter/jsontext
golang.org/x/crypto/internal/alias
golang.org/x/crypto/internal/poly1305
golang.org/x/crypto/chacha20
github.com/quic-go/quic-go/internal/utils
github.com/quic-go/quic-go/internal/qerr
github.com/quic-go/quic-go/qlogwriter
golang.org/x/crypto/chacha20poly1305
github.com/quic-go/quic-go/internal/wire
github.com/quic-go/quic-go/internal/flowcontrol
golang.org/x/crypto/hkdf
github.com/quic-go/quic-go/internal/utils/linkedlist
github.com/quic-go/quic-go/internal/utils/ringbuffer
golang.org/x/net/bpf
golang.org/x/net/internal/iana
golang.org/x/net/internal/socket
golang.org/x/text/transform
golang.org/x/text/unicode/bidi
github.com/quic-go/quic-go/qlog
golang.org/x/text/secure/bidirule
golang.org/x/net/ipv4
golang.org/x/net/ipv6
golang.org/x/text/unicode/norm
github.com/quic-go/quic-go/internal/congestion
github.com/quic-go/quic-go/internal/ackhandler
github.com/quic-go/quic-go/internal/handshake
golang.org/x/net/internal/httpsfv
github.com/quic-go/quic-go
github.com/heroku/x/hmetrics
golang.org/x/net/idna
github.com/heroku/x/hmetrics/onload
os/user
golang.org/x/net/http/httpguts
golang.org/x/net/internal/httpcommon
golang.org/x/net/http2
github.com/lib/pq/internal/pqutil
github.com/lib/pq/internal/pgpass
github.com/lib/pq/internal/pgservice
github.com/lib/pq/internal/pqsql
github.com/lib/pq/internal/pqtime
github.com/lib/pq/internal/proto
github.com/lib/pq/oid
github.com/lib/pq/pqerror
github.com/lib/pq/scram
github.com/lib/pq
golang.org/x/net/http2/h2c
github.com/quic-go/quic-go/http3/qlog
github.com/quic-go/quic-go/http3
github.com/gin-gonic/gin/binding
github.com/gin-gonic/gin/render
github.com/gin-gonic/gin
github.com/heroku/go-getting-started
$ heroku local --port=5006

Visit your application at http://localhost:5006/db. 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 76490a8] Added db route
 3 files changed, 47 insertions(+)

Now deploy as before:

$ git push heroku main
...

Finally, check that everything is working:

$ heroku open db

Provision a Logging Add-on

Beyond databases, add-ons provide many additional services for your application. In this step, you provision a free add-on to store your app’s logs.

By default, Heroku stores 1500 lines of logs from your application, but the full log stream is available as a service. Several add-on providers have logging services that provide things such as log persistence, search, and email and SMS alerts.

In this step, you provision one of these logging add-ons, Papertrail.

Provision the Papertrail logging add-on:

$ heroku addons:create papertrail
Creating papertrail on enigmatic-caverns-43977... free
Provisioning has been successful
Created papertrail-tapered-77678
Run heroku addons:docs papertrail to view documentation.

The add-on is now deployed and configured for your app. You can list add-ons for your app with this command:

$ heroku addons

 Add-on                                          Plan         Price         Max Price  State
-----------------------------------------------------------------------------------------------
 heroku-postgresql (postgresql-contoured-48546)  essential-0  ~$0.007/hour  $5/month   created
 └─ as DATABASE

 papertrail (papertrail-tapered-77678)           choklad      free          free       created
 └─ as PAPERTRAIL


The table above shows add-ons and the attachments to the current app (enigmatic-caverns-43977) or other apps.

To see this particular add-on in action, visit your application’s Heroku URL a few times. Each visit generates more log messages, which get routed to the Papertrail add-on. Visit the Papertrail console to see the log messages:

$ heroku addons:open papertrail

Your browser opens up a Papertrail web console, showing the latest log events. The interface lets you search and set up alerts.

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))

Now run goimports to automatically bring in the standard library packages that are now in use:

$ goimports -w main.go

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:

REPEAT=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 REPEAT=10
Setting REPEAT and restarting enigmatic-caverns-43977... done, v5
REPEAT: 10

View the app’s config vars using heroku config:

$ heroku config | grep REPEAT
REPEAT:       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:

  • How Heroku Works
  • Preparing a Codebase for Heroku Deployment
  • Heroku Go Documentation

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure
  • .NET

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing
  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Github
  • LinkedIn
  • © 2026 Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
  • heroku.com
  • Legal
  • Terms of Service
  • Privacy Information
  • Responsible Disclosure
  • Trust
  • Contact
  • Cookie Preferences
  • Your Privacy Choices