Deploying Go + React to Heroku using Docker, Part 1
By: Dean Baker
Tags: React, Docker, Tutorial, Golang, Heroku

Photo by Guillaume Bolduc on Unsplash

There are a bunch of how-tos and tutorials in doing most of what I am about to write about, but nothing that really pulled it all together for me. I wanted something I could play with to help learn Go, but also have the ability to push it live somewhere — Heroku is a great choice for a pet project like this. This is Part 1 of a 3 part series:

Part 1— deploying a simple server and client (this guide)

Part 2— adding a database

Part 3— adding user authentication

You can see the complete code here: github

What you’ll build

We will build a client in React.js and server in Go that we can deploy to Heroku using Docker. We will also have a decent local environment to develop under.

What you’ll need

You will need the following tools to begin:

Node for the client (Install)

Go for the server (Install)

Docker to deploy the thing (Install)

Heroku to host the thing (Free account, CLI tools)

IDE (I used VSCode)

Getting started

We will create a root directory that will hold separate client and server directories, the root will also hold the Dockerfile (this is a file, not a directory) used to build the final product.

project

|

|-client/

|-server/

|-Dockerfile

The Go server

First things first, let’s create an API for the front end to consume. In your server directory create a new Go module, and create an initial main.go file.

$ go mod init github.com/deandemo/react-go-heroku

create main.go file

Here we are using gin, a popular web framework to help with routing, this will come in handy a bit later.

If you would like to have a look at how the app behaves:

$ go run main.go # You will have to run this in a new terminal

$ curl localhost:8080/api/ping

Getting started with React

Disclaimer: The code generated here is likely to change as React and create-react-app evolves.

We will leverage facebook’s create-react-app tool to scaffold out a front end quickly, so jump into the root directory of your project and run:

$ npx create-react-app client

Since we are creating a mono-repo, please delete the git repository (.git) in the newly created /client directory.

PingComponent

Let’s create a component that can hit a server, in this case, we will use axios, a popular framework to help make API calls. Simply place the below file into the src directory of the react app, call this file PingComponent.js

Before this code is valid we must import the axios package:

# From the /client/ directory run

$ npm install axios --save

PingComponent.js

Notice on line 13 we are invoking an endpoint at api/ping , if we boot up the react app using npm start this would equate to hitting http://localhost:3000/api/ping — more on this later as we wire up our local environment.

App.js

Now we can hook up our ping component in the simplest way possible. In the App.js file that was created using the create-react-app tool, add the following:

App.js

Notice lines 4 and 23 where we import and use our PingComponent.

Proxy for local development

If we were to start up both of our codebases, we would have a client running on port 3000, and a server on port 8080. This isn’t ideal for development, so we can configure the client to proxy all unknown requests to the API server by modifying our package.json by adding a proxy field:

Running locally

Ok great, now we have a server and a client that has the ability to proxy requests to it. Let’s give it a go!

Open a terminal and start the server first (if it’s not already running), next start the client using npm start . This should automatically open the app in a browser and you should see success!

# From the /server directory

$ go run main.go # From the /client directory

$ npm start

Docker

Now we have a development environment, let's get this thing to “prod”.

Dockerfile

First, we will create a Docker container that will host our production code. That will be the executable Go program, as well as the production build of our React application. For this, we can use a multi-stage docker build, paste this text into a file called Dockerfile that lives in the root of our project.

# Build the Go API

FROM golang:latest AS builder

ADD . /app

WORKDIR /app/server

RUN go mod download

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w" -a -o /main . # Build the React application

FROM node:alpine AS node_builder

COPY --from=builder /app/client ./

RUN npm install

RUN npm run build # Final stage build, this will be the container

# that we will deploy to production

FROM alpine:latest

RUN apk --no-cache add ca-certificates

COPY --from=builder /main ./

COPY --from=node_builder /build ./web

RUN chmod +x ./main

EXPOSE 8080

CMD ./main

As you can see here, we are actually creating 3 docker containers, the first to build the Go API, the second for the React application, and the third to create a lightweight container that contains only production code — no need to ship dependencies and tooling that is only needed at build time.

Before we give it a red hot go, a little tip here is to create a .dockerignore file to ensure that we don’t push unnecessary files to the docker context. In our case, the client/node_modules file here is quite large. So let’s add this to the .dockerignore file.

$ echo '**/node_modules' > .dockerignore

Running a docker container

$ docker build -t golang-heroku .

$ docker run -p 3000:8080 -d golang-heroku

docker build will create an image with a tag golang-heroku , you can now run an instance of this image using the run command. Notice that the docker run command is forwarding our local port 3000 and forwarding this onto port 8080 in the container, this means we can now have a look at our work locally. Navigate to http://localhost:3000 to see your website.

Heroku

We now have a local build of what we would like Heroku to serve, this is a straightforward process, every time we push our code to the Heroku remote a new build will be started. Heroku will take care of the docker build and deployments for us!

I won’t go into too much detail, but let's assume that you have already created a Heroku account, and installed the CLI tools. Please ensure that you have logged in:

$ heroku login

In the root of the project, add a new file called heroku.yml — this will tell Heroku how to deploy our application.

build:

docker:

web: Dockerfile

worker:

dockerfile: Dockerfile

Git

If you haven’t already, we want to create a git repository in the root of the project. Please ensure that you have removed the client/.git directory to avoid git confusion.

$ git init

$ git add .

$ git commit -m 'Initial Commit'

These commands should create an initial repository with a single commit that contains all of our good work.

Now let's create a new Heroku application :)

heroku create

Creating app... done, ⬢ hidden-chamber-90878

https://hidden-chamber-90878.herokuapp.com/ | https://git.heroku.com/hidden-chamber-90878.git Creating app... done, ⬢ hidden-chamber-90878 $ heroku stack:set container

Stack set. Next release on ⬢ hidden-chamber-90878 will use container.

Run git push heroku master to create a new release on ⬢ hidden-chamber-90878.

First, we created a new application, you can see the proposed URL and git remote (the cli tool has added a Heroku remote to your .git/config ). Then we need to tell Heroku that we intend to deploy a container to this stack. Once this is done we can go ahead and deploy.

$ git add .

$ git commit -m 'Initial commit'

$ git push heroku master # If you need to retrieve your prod url etc

$ heroku apps:info

This is where the magic of a PaaS really happens! I change a small bit of code, some HTML perhaps, and when I push it to the heroku remote, it just tucks that ball and does a whole bunch of work to create a new immutable container to deploy for me.

The idea of pushing small git commits over the network (like my mobile connection on the train) and have Heroku build and deploy for me is pretty exciting!

Summary

I think this is a great first start, having the ability to work easily locally, and push changes to production at will (and have the platform take care of provisioning compute and deploying the thing).

But a dumb API and client just doesn’t cut it. In Part 2, we’ll look at provisioning a Heroku Postgres add-on, get our local development up to scratch, and ensure we can push and migrate the database at release time using Heroku’s release stage of the deployment process.