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.
Related Content:
- Building a REST CRUD app with Golang Revel
- How to install Go in Fedora and Rocky Linux/Centos/RHEL
- How to install Go (Golang) in Arch Linux/Manjaro
- Managing Database migrations with Golang goose using incremental SQL changes
In this guide we are going to check how to create a:
- Developent Docker file that hot reloads code changes
- Production Docker file that runs the compiled code package
- 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 (
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.gz
with 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.