Deploying Go + React to Heroku using Docker, Part 1Dean BakerBlockedUnblockFollowFollowingMay 9Photo by Guillaume Bolduc on UnsplashThere 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 databasePart 3— adding user authenticationYou can see the complete code here: githubWhat you’ll buildWe 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 needYou 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 startedWe 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/ |-DockerfileThe Go serverFirst 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 mod init github.
go fileHere 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/pingGetting started with ReactDisclaimer: The code generated here is likely to change as React and create-react-app evolves.
We will leverage the facebooks 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 clientSince we are creating a mono-repo, please delete the git repository (.
git) in the newly created /client directory.
PingComponentLet’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.
jsBefore this code is valid we must import the axios package:# From the /client/ directory run$ npm install axios –savePingComponent.
jsNotice 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.
jsNow 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:App.
jsNotice lines 4, and 23 where we import and use our PingComponent.
Proxy for local developmentIf 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:"proxy": "http://localhost:8080"Running locallyOk 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 startDockerNow we have a development environment, let's get this thing to “prod”.
DockerfileFirst, 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 APIFROM golang:latest AS builderADD .
/appWORKDIR /app/serverRUN go mod downloadRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w" -a -o /main .
# Build the React applicationFROM node:alpine AS node_builderCOPY –from=builder /app/client .
/RUN npm installRUN npm run build# Final stage build, this will be the container# that we will deploy to productionFROM alpine:latestRUN apk –no-cache add ca-certificatesCOPY –from=builder /main .
/COPY –from=node_builder /build .
/webRUN chmod +x .
/mainEXPOSE 8080CMD .
/mainAs 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' > .
dockerignoreRunning a docker container$ docker build -t golang-heroku .
$ docker run -p 3000:8080 -d golang-herokudocker 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.
HerokuWe 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 loginIn 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: DockerfileGitIf 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 createCreating app.
done, ⬢ hidden-chamber-90878https://hidden-chamber-90878.
com/ | https://git.
git$ heroku stack:set containerStack 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 .
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:infoThis 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!SummaryI 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.