The , often referred to as "golang", has gained a lot of well-deserved traction in the DevOps community. Many of the most popular tools such as , , and are written in Go, but it can also be a great choice for building web applications and APIs. Go programming language Docker Kubernetes Terraform Go provides you with all the speed and performance of a compiled language, but feels like coding in an interpreted language. This comes down to the great tooling that you get out of the box with a compiler, runner, test suite and code formatter provided by default. Add to that the robust and easily comprehensible approach to concurrency, to take maximum advantage of today's multi-core or multi-cpu execution environments, and it's clear why the Go community has grown so rapidly. Go feels like it was created with specific goals in mind, leading to a deceptively simple language design that's straightforward to learn, without compromising on capabilities. In this post, I'm going to show you how easy it is to develop a simple web application in Go, package it as a lightweight Docker image, and deploy it to . I'll also show a fairly new feature of Go: built-in package management. Heroku Go Modules I'm going to use Go's built-in module support for this article. Go 1.0 was released in March 2012. Up until version 1.11 (released in August 2018), developing Go applications involved for each "workspace", analogous to java's , and all of your Go source code and any third-party libraries were stored below the . managing a GOPATH JAVA_HOME GOPATH I always found this a bit off-putting, compared to developing code in languages like Ruby or Javascript where I could have a simpler directory structure isolating each project. In both of those languages, a single file ( for Ruby, for Javascript) lists all the external libraries, and the package manager keeps track of managing and installing dependencies for me. Gemfile package.json I'm not saying you can't manage the environment variable to isolate projects from one another. I particularly find the package manager approach is easier. GOPATH Thankfully, Go now has excellent package management built in, so this is no longer a problem. However, you might find mentioned in many older blog posts and articles, which can be a little confusing. GOPATH Hello, World! Let's get started on our web application. As usual, this is going to be a very simple "Hello, World!" app, because I want to focus on the development and deployment process, and keep this article to a reasonable length. Pre-requisites You'll need: A recent version of golang (I'm using 1.14.9.) Docker A account (The free account works fine for this example.) Heroku The Heroku command-line client git Go mod init To create our new project, we need to create a directory for it, and use the command to initialize it as a Go module. go mod init mkdir helloworld cd helloworld go mod init digitalronin/helloworld It's common practice to use your github username to keep your project names globally unique, and avoid name conflicts with any of your project dependencies, but you can use any name you like. You'll see a file in the directory now. This is where Go will track any project dependencies. If you look at the contents of the file, they should look something like this: go.mod digitalronin/helloworld go module 1.14 Let's start committing our changes: git init git add * git commit -m "Initial commit" gin We're going to use for our web application. Gin is a lightweight web framework, similar to for Ruby, for Javascript, or for Python. gin Sinatra express.js Flask Create a file called containing this code: hello.go package main func main() { := gin.Default() r.GET( , func(c *gin.Context) { c.String( , ) }) r.Run( ) } import "github.com/gin-gonic/gin" r "/hello" 200 "Hello, World!" ":3000" Let's break this down a little: r := gin.Default() This creates a router object, , using the built-in defaults that come with gin. r Then, we assign a handler function to be called for any HTTP GET requests to the path , and to return the string "Hello, World!" and a 200 (HTTP OK) status code: /hello r.GET( , func(c *gin.Context) { c.String( , ) }) "/hello" 200 "Hello, World!" Finally, we start our webserver and tell it to listen on port 3000: r.Run( ) ":3000" To run this code, execute: go run hello.go You should see output like this: go: finding package github.com/gin-gonic/gin go: found github.com/gin-gonic/gin github.com/gin-gonic/gin v1 [GIN-debug] [WARNING] Creating an Engine instance the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running mode. Switch to mode production. - using env: GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /hello --> main.main.func1 ( handlers) [GIN-debug] Listening and serving HTTP on : module for in .6 .3 with in "debug" "release" in export 3 3000 Now if you visit in your web browser, you should see the message "Hello, World!" http://localhost:3000/hello Notice that we didn't have to install gin separately, or even edit our file to declare it as a dependency. Go figures that out and makes the necessary changes for us, which is what's happening when we see these lines in the output: go.mod go: finding package github.com/gin-gonic/gin go: found github.com/gin-gonic/gin github.com/gin-gonic/gin v1 module for in .6 .3 If you look at the file, you'll see it now contains this: go.mod digitalronin/helloworld go github.com/gin-gonic/gin v1 module 1.14 require .6 .3 // indirect You will also see a file now. This is a text file containing references to the specific versions of all the package dependencies, and their dependencies, along with a cryptographic hash of the contents of that version of the relevant module. go.sum The file serves a similar function to for a Javascript project, or in a Ruby project, and you should always check it into version control along with your source code. go.sum package-lock.json Gemfile.lock Let's do that now: git add * git commit -m "Add 'Hello world' web server" Serving HTML and JSON I'm not going very far into what you can build with gin, but I do want to demonstrate a little more of its functionality. In particular, sending JSON responses and serving static files. Let's look at JSON responses first. Add the following code to your file, right after the block: hello.go r.GET api := r.Group( ) api.GET( , func(c *gin.Context) { c.JSON( , gin.H{ : , }) }) "/api" "/ping" 200 "message" "pong" Here we're creating a "group" of routes behind the path with a single path which will return a JSON response. /api /ping With this code in place, run the server with and then hit the new API endpoint: go run curl http: //localhost:3000/api/ping You should get the response: { : } "message" "pong" Finally, let's make our webserver serve static files. Gin has an additional library for this. Change the import block at the top of the file to this: hello.go ( ) import "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" The most popular code editors have golang support packages you can install which will take care of the declarations for you automatically, updating them for you whenever you use a new module in your code. import Then, add this line inside the function: main r.Use( .Serve( , .LocalFile( , ))) static "/" static "./views" true The full code for our web application now looks like this: hello.go package main ( ) func main() { := gin.Default() r.GET( , func(c *gin.Context) { c.String( , ) }) api := r.Group( ) api.GET( , func(c *gin.Context) { c.JSON( , gin.H{ : , }) }) r.Use( .Serve( , .LocalFile( , ))) r.Run() } import "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" r "/hello" 200 "Hello, World!" "/api" "/ping" 200 "message" "pong" static "/" static "./views" true The line enables our webserver to serve any static files from the directory, so let's add a few: r.Use(static.Serve... views mkdir -p views/css views/css/stylesheet.css body { font-family: Arial; } h1 { : red; } color views/index.html <html> <link rel="stylesheet" href="/css/stylesheet.css" /> </head> <body> <h1>Hello, World!</h1> </body> < > head </ > html Now restart the webserver using and visit and you should see the styled message: go run hello.go http://localhost:3000 Dockerize We've written our go web application, now let's package it up as a Docker image. We could create it as a Heroku buildpack, but one of the nice features of Go is that you can distribute your software as a single binary file. This is an area where Go really shines, and using a Docker-based Heroku deployment lets us take advantage of that. Also, this technique isn't limited to Go applications: You can use Docker-based deployment to Heroku for projects in any language. So, it's a good technique to understand. So far, we've been running our code with the command. To compile it into a single, executable binary, we simply run: go run go build This will compile all our Go source code and create a single file. By default, the output file will be named according to the module name, so in our case it will be called . helloworld We can run this: ./helloworld And we can hit the same HTTP endpoints as before, either with or our web browser. curl The static files are not compiled into the binary, so if you put the file in a different directory, it will not find the directory to serve our HTML and CSS content. helloworld views That's all we need to do to create a binary for whatever platform we're developing on (in my case, my Mac laptop). However, to run inside a Docker container (for eventual deployment to Heroku) we need to compile a binary for whatever architecture our Docker container will run on. I'm going to use , so let's build our binary on that OS. Create a with the following content: alpine linux Dockerfile FROM golang: -alpine RUN mkdir /build ADD go.mod go.sum hello.go /build/ WORKDIR /build RUN go build 1.14 .9 In this image, we start with the base image, add our source code, and run to create our binary. golang go build helloworld We can build our Docker image like this: docker build -t helloworld . Don't forget the trailing at the end of that command. It tells Docker we want to use the current directory as the build context. . This creates a Docker image with our binary in it, but it also contains all the Go tools needed to our code, and we don't want any of that in our final image for deployment, because it makes the image unnecessarily large. It can also be a security risk to install unnecessary executables on your Docker images. helloworld compile We can see the size of our Docker image like this: $ docker images helloworld REPOSITORY TAG IMAGE ID CREATED SIZE helloworld latest ec1ca905 minutes ago MB 9657 4 370 For comparison, the image (a lightweight linux distribution, often used as a base for docker images) is much smaller: alpine $ docker images alpine REPOSITORY TAG IMAGE ID CREATED SIZE alpine latest caf27325b298 months ago MB 20 5.53 On my Mac, the binary is around 14MB, so the golang image is much bigger than it needs to be. helloworld What we want to do is use this Dockerfile to our helloworld binary to run on alpine linux, then copy the compiled binary into an alpine base image, without all the extra golang tools. build We can do this using a "multistage" Docker build. Change the Dockerfile to look like this: FROM golang: -alpine AS builder RUN mkdir /build ADD go.mod go.sum hello.go /build/ WORKDIR /build RUN go build FROM alpine RUN adduser -S -D -H -h /app appuser USER appuser COPY -- =builder /build/helloworld /app/ COPY views/ views WORKDIR /app CMD [ ] 1.14 .9 from /app/ "./helloworld" On the first line, we label our initial Docker image . AS builder Later, we switch to a different base image and then copy the binary from our builder image like this: FROM alpine helloworld COPY -- =builder /build/helloworld /app/ from Build the new Docker image: docker build -t helloworld . Now, it's the size you would expect for a base alpine image plus our helloworld binary: $ docker images helloworld REPOSITORY TAG IMAGE ID CREATED SIZE helloworld latest d6d9cb64c7e seconds ago MB 1 8 20.7 We can run our webserver from the Docker image like this. (If you have another version running using or , you'll need to stop that one first, to free up port 3000.) go run hello.go ./helloworld docker run --rm -p : helloworld 3000 3000 The dockerized webserver should behave just like the and versions that it has its own copies of the static files. So, if you change any of the files in you won't see the changes until you rebuild the Docker image and restart the container. go run hello.go ./helloworld except views/ Deploy to Heroku Now that we have our dockerized web application, let's deploy it to Heroku. Heroku is a PaaS provider that makes it simple to deploy and host an application. You can set up and deploy your application through the Heroku UI, or through the Heroku CLI. For this example, we'll use the Heroku command-line application. Getting PORT from an environment variable We've hard-coded our webserver to run on port 3000, but that won't work on Heroku. Instead, we need to alter it to run on whichever port number is specified in the environment variable, which Heroku will supply automatically. PORT To do this, alter the line near the bottom of our file, and remove the string value so the line becomes: r.Run hello.go ":3000" r.Run() The default behavior of gin is to run on whatever port is in the environment variable (or port 8080 if nothing is specified). This is exactly the behavior Heroku needs. PORT Setting up our Heroku app First, log into Heroku: heroku login Now, create an app: heroku create Tell Heroku we want to build this project using a Dockerfile, rather than a buildpack: heroku stack:set container To do this, we also need to create a file like this: heroku.yml build: docker: web: Dockerfile run: web: ./helloworld The file is a manifest that defines our app and allows us to specify add-ons and config vars to use during app provisioning. heroku.yml Next, add git and commit these files, then push to heroku to deploy: git push heroku main My git configuration uses as the default branch. If your default branch is called , then run instead. main master git push heroku master You should see Heroku building the image from your Dockerfile, and pushing it to the Heroku Docker registry. Once the command completes, you can view the deployed application in your browser by running: heroku open Conclusion To recap, here's a summary of what we covered today: Creating a golang web application, using go modules and the gin web framework to serve strings, JSON, and static files Using a multistage Dockerfile to create a lightweight Docker image Deploying a Docker-based application to Heroku using and a file heroku stack:set container heroku.yml I've only scratched the surface in this article on how to use Go to build web applications and APIs. I hope this gives you enough to get started on your own golang web applications. Also published at https://dev.to/heroku/deploying-your-first-golang-webapp-11b3