How to run a Golang Revel App with Docker and Docker Compose

In this guide we are going to learn how to run a golang revel app using docker and docker-compose. Golang revel is a high productivity, full-stack web framework for theĀ GoĀ language. Checkout the code used here in this github repo here.

In this guide we are going to check how to create a:

  1. Developent Docker file that hot reloads code changes
  2. Production Docker file that runs the compiled code package
  3. Docker Compose file that can be used to bring up the service and

Development Dockerfile

For the dev environment, we want to build a docker image with the current code in it and the dependencies installed then run it while mounting the current code directory to the working directory of the app. That way when we run the container and make local changes, the code changes will reflect in the container as well. This is what is normally known as hot reload.

I have the file named as Dockerfile.local in my case. This is its content.

FROM golang:1.17.2-alpine AS build

# Add required packages
RUN apk add --update git curl bash

# Install revel framework
RUN go get -u github.com/revel/revel
RUN go get -u github.com/revel/cmd/revel

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

ENV CGO_ENABLED 0

ADD . .

ENTRYPOINT revel run

The above Dockerfile builds from Golang image

golang:1.17.2-alpine. We install some packages (

git curl bash) using apk then install the revel framework and revel command line tool using `go get`.

We then set the working directory to

/app and copy golang dependencies files

go.mod and go.sum and install the dependencies using go mod download. The advantage of copying only the dependencies file is that everytime we change the codebase or add new files, we do not have to recreate that build stage.

Finally, we add the code and use revel run as our entrypoint. Revel run creates a proxy container to run your application in, it also can watch your file for changes and if any changes are made it can redeploy the application (if Go source files are changed), or recompile the templates. It also downloads all necessary libraries

Running In dev with Docker Compose

We are going to use Docker Compose to run our app.

Docker Compose allows us to define and run multi-container applications. We will use Docker compose in out case to run the app container and connect with the database.

This is our docker-compose.yaml

version: '3.9'

services:
  go-revel-crud:
    build:
      context: .
      dockerfile: ./Dockerfile.local
    ports:
      - 8090:8090
    volumes:
      - .:/app
    environment:
      - ENV=dev
      - PORT=8090
      - DB_URL=postgres://go-revel-crud:go-revel-crud@postgres/go-revel-crud?sslmode=disable

  postgres:
    image: postgres:14.0-alpine
    ports:
      - 5432:5432
    volumes:
      - ~/apps/go-revel-crud/pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=go-revel-crud
      - POSTGRES_USER=go-revel-crud
      - POSTGRES_DB=go-revel-crud

Here, we are defining two docker containers. We define our app cotainer

go-revel-crud and a database container `postgres`. For our app container, we are defining build instructions to use the

./Dockerfile.local we created earlier. We are also mapping the app port

8090 so we can access the app locally. We are also mapping the local path . to the container path /app where we set as our working directory earlier. The finally we are defining some env variables.

For the postgres container, we are using Postgres 14.0 alpine image to create a container with some initial user and password defined using the env variables.

To build the app image, use this command:

docker-compose build

Output:

➜ docker-compose build
[+] Building 10.6s (14/14) FINISHED
 => [internal] load build definition from Dockerfile.local                                                                        0.4s
 => => transferring dockerfile: 38B                                                                                               0.0s
 => [internal] load .dockerignore                                                                                                 0.5s
 => => transferring context: 2B                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/golang:1.17.2-alpine                                                           4.5s
 => [auth] library/golang:pull token for registry-1.docker.io                                                                     0.0s
 => [1/8] FROM docker.io/library/golang:1.17.2-alpine@sha256:5519c8752f6b53fc8818dc46e9fda628c99c4e8fd2d2f1df71e1f184e71f47dc     0.0s
 => [internal] load build context                                                                                                 2.4s
 => => transferring context: 27.28MB                                                                                              2.1s
 => CACHED [2/8] RUN apk add --update git curl bash                                                                               0.0s
 => CACHED [3/8] RUN go get -u github.com/revel/revel                                                                             0.0s
 => CACHED [4/8] RUN go get -u github.com/revel/cmd/revel                                                                         0.0s
 => CACHED [5/8] WORKDIR /app                                                                                                     0.0s
 => CACHED [6/8] COPY go.mod go.sum ./                                                                                            0.0s
 => CACHED [7/8] RUN go mod download                                                                                              0.0s
 => [8/8] ADD . .                                                                                                                 0.8s
 => exporting to image                                                                                                            1.7s
 => => exporting layers                                                                                                           1.2s
 => => writing image sha256:4941b7b393244225b447d1208d2675bb1e1fe88775890c7a689102022ecc81f1                                      0.0s
 => => naming to docker.io/library/go-revel-crud_go-revel-crud                                                                    0.0s

The run the containers using this comand:

docker-compose up -d

Output:

➜ docker-compose up -d
[+] Running 2/2
 ⠿ Container go-revel-crud-go-revel-crud-1  Started                                                                               2.9s
 ⠿ Container go-revel-crud-postgres-1       Started                                                                               3.0s

Check that the containers are running using the ps command:

➜ docker-compose ps
NAME                            COMMAND                  SERVICE             STATUS              PORTS
go-revel-crud-go-revel-crud-1   "/bin/sh -c 'revel r…"   go-revel-crud       running             0.0.0.0:8090->8090/tcp
go-revel-crud-postgres-1        "docker-entrypoint.s…"   postgres            running             0.0.0.0:5432->5432/tcp

We can test our app by doing a curl to the healthcheck endpoint:

❯ curl http://127.0.0.1:8090/health

{
  "build_time": "2021-11-03T20:08:39Z",
  "db": {
    "hello": "1",
    "type": "postgres",
    "up": true,
    "version": "PostgreSQL 14.0 on x86_64-pc-linux-musl, compiled by gcc (Alpine 10.3.1_git20210424) 10.3.1 20210424, 64-bit"
  },
  "server": {
    "compiler": "gc",
    "cpu": 6,
    "goarch": "amd64",
    "goos": "linux",
    "goroutines": 10,
    "hostname": "c29e8b6de44e",
    "memory": {
      "alloc": "1 MB",
      "num_gc": 1,
      "sys": "13 MB",
      "total_alloc": "2 MB"
    }
  },
  "status": 200,
  "success": true,
  "time": {
    "now": "2021-11-04T07:36:54.428658947Z",
    "offset": 0,
    "timezone": "UTC"
  },
  "version": "git-598db8e-dirty"
}

We can now work on our app locally, the changes will be watched and compiled in the container.

Check the logs using this command:

docker-compose logs -f

Production Docker file that runs the compiled code package {.wp-block-heading}

For production build, we would want to run the resultant package of compiling the golang code. Golang revel has a package command that creates a

.tar.gzwith the executable and a run.sh that will start the app on the defined port.

Here is the docker file:

#Compile stage
FROM golang:1.16.5-alpine AS builder

# Add required packages
RUN apk add  --no-cache --update git curl bash

RUN go get -u github.com/revel/revel
RUN go get -u github.com/revel/cmd/revel

WORKDIR /app
ADD go.mod go.sum ./
RUN go mod download
ENV CGO_ENABLED 0 \
    GOOS=linux \
    GOARCH=amd64
ADD . .

RUN revel package .

# Run stage
FROM alpine:3.13
RUN apk update && \
    apk add mailcap tzdata && \
    rm /var/cache/apk/*
WORKDIR /app
COPY --from=builder /app/app.tar.gz .
RUN tar -xzvf app.tar.gz && rm app.tar.gz
ENTRYPOINT /app/run.sh

The most notable thing here is the different stages. Since go produces an executable, there is no need to have a runtime and the code in the image we deploy to a production environment. Thatn is why we have a first stage where we have a goalang runtime that we use to install dependencies and build the app.

In the second stage, we use a minimal alpine image to extract the package created in the intial stage then define entry point to be the run.sh script.

Here is the updated docker compose file if we want to run the image created.

version: '3.9'

services:
  go-revel-crud:
    image: go-revel-crud:latest
    ports:
      - 8090:8090
    volumes:
      - .:/app
    environment:
      - ENV=dev
      - PORT=8090
      - DB_URL=postgres://go-revel-crud:go-revel-crud@postgres/go-revel-crud?sslmode=disable

  postgres:
    image: postgres:14.0-alpine
    ports:
      - 5432:5432
    volumes:
      - ~/apps/go-revel-crud/pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=go-revel-crud
      - POSTGRES_USER=go-revel-crud
      - POSTGRES_DB=go-revel-crud

Please note that instead of the build that we had earlier, we are specifying an image that was built from the Dockerfile.

Conclusion

In this tutorial, we explored how to run a golang revel app locally and how we can build an image for production like setting.

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy