01 - Setup Go Server with Reload in Docker
Tags: go, docker, tutorial, traefik
Published: 2020-09-25 01:05:39

## Project Overview

This article is the beginning of what I hope to be a "several"-part tutorial on setting up a memorization app. The client application for managing memorization objects (we'll call them "memthings") will be built with React/Typescript. The API for the backend application managing these "memthings" will be built with Node/Typescript. User accounts and authentication will be managed by a separate application with its UI built in Vue and its backend built in Go. All of these applications will be setup to run inside of docker containers and will be "hosted" on the same test domain with routing provided by Traefik. *Congrats if you made it through this paragraph. Phew!*

Here's an overview of the application, along with a video demo of what we'll be building! Today, we'll be working at scaffolding out the application in the upper-left corner, along with properly routing requests to this container with Traefik.

![App Overview](https://dev-to-uploads.s3.amazonaws.com/i/i3v6bv1ulriwc08on2cm.png)

And for those of you that prefer a video version of this tutorial.

{% youtube 00YqXu8U1zQ %}

If anything is unclear or I have made any errors, please find the functioning code on [Github](https://github.com/JacobSNGoodwin/memrizr).

Vamos!

## Creating Directories

For reference, your directory structure will be as follows by the end of this tutorial (minus the readme content).

![Folder Structure](https://dev-to-uploads.s3.amazonaws.com/i/jnwu5w8rk24gzuxwvurd.PNG)

To begin begin scaffolding out the application, we'll need to create a folder which will contain all of our sub-projects along with application-wide configuration files (.env, docker-compose.yml). We do this by creating a root _memrizr_ folder (it's VERRRRRY important to do cutesy re-spellings when creating an app). We also create an account directory where we'll write our Golang application.

```bash
mkdir memrizr

cd memrizr

git init

mkdir account
```

Next, we'll create a workspace file in the root folder called _memrizr.code-workspace_. This file allows us to create a ["multi-root" workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) in VS Code. While this step is not required, it is helpful when working with Golang projects which use GO Modules (with a go.mod file to list dependencies). This is because the Go Developer tools (language server) will only function if the _go.mod_ file is at the project's root. There are [requests to update](https://github.com/golang/go/issues/32394) this (which would eliminate the problem below), but it would appear to be a difficult task. 

We add the following content to the _memrizr.code-workspace_ file. This adds the _account_ folder and the root folder as workspaces. While I would prefer to merely add root application files to the workspace, VS Code doesn't currently support this. And from this [Github issue](https://github.com/microsoft/vscode/issues/45177), it doesn't look like they're keen on making this work any time soon. Alas, maybe you're a fortunate soul reading this two years down the line when neither of the above challenges exists!

```json
{
  "folders": [
    {
      "path": "./account"
    },
    {
      "path": "."
    }
  ]
}
```

## Adding _main.go_

We'll now move into our account folder, initialize go modules, and add _gin_, a web framework and http router written in, and for, Golang. When running the commands below, make sure to replace the name of my Github repository with your own when initializing the module.

```bash
cd account

go mod init github.com/jacobsngoodwin/memrizr

go get -u github.com/gin-gonic/gin
```

We'll now create the main go file. In this file we:
  * Create a _router_ for handling http requests to different paths. For now, we create a single _GET_ route to "/api/account", along with its accompanying handler function.
  * Next, we create a standard Golang http server server struct, applying the gin router we just created.
  * Finally, we run the server in a go routine. You can learn more about graceful shutdown of servers in the link in the code. In essence, the code creates a channel called _quit_ which listens for termination signals (like ctrl-c). The `<- quit` line blocks subsequent code until such a termination signal is received, after which the server is shut down. 

```golang
package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	// you could insert your favorite logger here for structured or leveled logging
	log.Println("Starting server...")

	router := gin.Default()

	router.GET("/api/account", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"hello": "world",
		})
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: router,
  }

	// Graceful server shutdown - https://github.com/gin-gonic/examples/blob/master/graceful-shutdown/graceful-shutdown/server.go
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("Failed to initialize server: %v\n", err)
		}
	}()

	log.Printf("Listening on port %v\n", srv.Addr)

	// Wait for kill signal of channel
	quit := make(chan os.Signal)

	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

	// This blocks until a signal is passed into the quit channel
	<-quit

	// The context is used to inform the server it has 5 seconds to finish
	// the request it is currently handling
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Shutdown server
	log.Println("Shutting down server...")
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatalf("Server forced to shutdown: %v\n", err)
}
```

You can no test this by running `go run main.go` inside of the _account_ folder.

## Setup Docker

Let's create a `Dockerfile` (as written) inside of our _account_ directory. This Dockerfile will make use of stages. The first stage, _builder_, is where we download and install all dependencies and then build ths application binary. We'll also use this _builder_ stage in our _docker-compose_ development environment later for auto-reloading our application. We'll do this by means of the application called _reflex_, which we download in the builder stage.

In the second half of the Dockerfile, we extract the built application from builder, and run it in a separate container. This eliminates all of the unnecessary code from the builder stage and creates a leaner final application container which is ready for deployment. 

```Dockerfile
FROM golang:alpine as builder

WORKDIR /go/src/app

# Get Reflex for live reload in dev
ENV GO111MODULE=on
RUN go get github.com/cespare/reflex

COPY go.mod .
COPY go.sum .

RUN go mod download

COPY . .

RUN go build -o ./run .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/

#Copy executable from builder
COPY --from=builder /go/src/app/run .

EXPOSE 8080
CMD ["./run"]
```

## Creating docker-compose file
Now we'll create a docker-compose.yml file. This file can be used to spin-up several docker-containers for development by simply running the command `docker-compose up` in the same folder as this file (by default, though command-line args can be used to define a filepath, as well). We'll save this file in the root folder of the project.

In this file we create two _services_. 

* _reverse-proxy_ - [Traefik](https://doc.traefik.io/traefik/)
  * This service will be running the Traefik docker image, the reverse proxy we'll be using to route http requests to the 4 applications we'll be creating. 
  * We configure Traefik with an array of commands which correspond to command line arguments. For this course, we'll not be setting up TLS certificates, and therefor set `--api.insecure=true`. The next two arguments tell Traefik to look for configurations in docker containers. Normally, however, Traefik will automatically expose Docker containers with some defaults. We will disable this by setting with `--providers.docker.exposedByDefault=false`. 
  * This service's port mappings are configured to let us access the standard http port (80) from the docker host (our machine), and also to access a nice dashboard provided by Traefik on port 8080.
* _account_ - Go application in _account_ folder
  * First off, take note of the _build_ key in this file. We tell docker-compose that we want to create a container found in the _account_ folder. However, remember how the docker file used a "multi-stage" build? For development, we don't need to run the final, lean build. Therefore, we set `target: builder` to tell docker-compose to create a container only using the first build stage labeled as _builder_ in the Dockerfile. 
  * We add some labels, which is how we setup Docker containers to be automatically detected by Traefik. Since we are not exposing all containers by default, we must explicitly set `"traefik.enable=true"`. The next label is also very important. This sets routing rules, telling Traefik to route any http requests to `malcorp.test/api/account` to our account application.
  * Finally, recall that we installed a tool called [_reflex_](https://github.com/cespare/reflex) in our Dockerfile. This tool watches for any changes to *.go files (with a regular expression in this case), and then reruns the main.go file. We actually use `go run ./` instead of `go run main.go` as we will have multiple *.go files in the root directory. 

```yaml
version: "3.8"
services:
  reverse-proxy:
    # The official v2 Traefik docker image
    image: traefik:v2.2
    # Enables the web UI and tells Traefik to listen to docker
    command:
      - "--api.insecure=true"
      - "--providers.docker"
      - "--providers.docker.exposedByDefault=false"
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
  account:
    build:
      context: ./account
      target: builder
    image: account
    expose:
      - "8080"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.account.rule=Host(`malcorp.test`) && PathPrefix(`/api/account`)"
    environment:
      - ENV=dev
    volumes:
      - ./account:/go/src/app
    # have to use $$ (double-dollar) so docker doesn't try to substitute a variable
    command: reflex -r "\.go$$" -s -- sh -c "go run ./"
```

## Add test domain to hosts file
In order to use the malcorp.test domain for development, we must add it to our hosts file so that when we enter it into our browser, curl, postman, etc., it resolves to our local machine. You can do this by adding the following line to _/etc/hosts_ on Mac/Linux or _c:\windows\system32\drivers\etc\hosts_ (or possibly both if you're using Windows Subsystem for Linux). In either case, you will need to save the file with administrative privileges (sudo on Mac/Linux)

```text
127.0.0.1       malcorp.test
```

### Running the application 🤞🏼

Welp... here goes nothin'! Let's give it a try. In the root folder, run `docker-compose up` (or `docker-compose up --build` if you ever need a fresh rebuild of containers).

Now you can open your browser and type `http://malcorp.test/api/account` and you should receive the JSON response

```json
{"hello":"space peoples"}
```

Now try changing the response in main.go. You should see your server restart, refresh the browser, and see your new response. 

### Conclusion
Wowza! That was a hell of a lot!

Next time we'll add routes to our gin router and cover some details of the account application's architecture. 

¡Hasta la próxima!