Do you want to write a Golang app which is containerized with Docker? The purpose of this article is to help you quickly get your Golang App containerized for development (with hot-reload) and production purposes.
Before you begin
Please install Docker Desktop before proceeding any further. Once installed, start the desktop app and if it works you are ready to begin.
Also I am assuming you have a $GOPATH
directory where you put your Golang source code you are working on. Mien will be: ~/go/src/github.com/bartmika
.
… but if I am unfamiliar with Docker?
Don’t worry! Docker is a mature toolset which has been around long enough for there to be plenty of great educational resources to help you learn. I recommend the following as it helped me:
Checkout another article I wrote which will help you learn the basics:
Golang and Docker for Development with Hot Reloading
In this section you’ll learn how to setup your Golang app for local development on your machine. The purpose of a development container is to hold all the dependencies (ex: Third-Party Packages like GORM), infrastructure (ex: Database, Memory Cache, etc) and your code in manner that assist and improves your development.
Create our app repo.
mkdir mullberry-backend cd mullberry-backend go mod init github.com/bartmika/mullberry-backend
Every time you want to add a dependency you would shutdown the current running container and install the dependencies. Install our dependencies as follows:
go get github.com/labstack/echo/v4 go get github.com/labstack/echo/v4/middleware@v4.7.2
Hold on, you need to have Golang installed locally? What’s the point of the container then? This Stackoverflow user explains it best in a discussion of setting up a
Dockerfile
for development purposes:[It] is great for when you want to package your app into a container, ready for deployment. It’s not so good for development where you want to have the source outside the container and have the running container react to changes in the source.*
So in essence, if you are OK with this limitation then proceed onwards!
Create a
main.go
file in the root of your project then copy and paste the following into it:package main import ( "net/http" "os" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) func main() { e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.GET("/", func(c echo.Context) error { return c.HTML(http.StatusOK, "Hello, Docker! <3") }) e.GET("/ping", func(c echo.Context) error { return c.JSON(http.StatusOK, struct{ Status string }{Status: "OK"}) }) httpPort := os.Getenv("HTTP_PORT") if httpPort == "" { httpPort = "8080" } e.Logger.Fatal(e.Start(":" + httpPort)) }
Please note, the above code was copied from “Build your Go image” via their Docker Documentation.
Before creating anything docker related, create a
.dockerignore
file and copy and paste the following contents:bin .dockerignore Dockerfile docker-compose.yml dev.Dockerfile dev.docker-compose.yml .env
What is a
.dockerignore
file? In essence it’s similar to.gitignore
in which certain files / folders will not be saved to the docker container and image.Create the
dev.Dockerfile
file and copy and paste the following contents into it.FROM golang:1.18 # Copy application data into image COPY . /go/src/bartmika/mullberry-backend WORKDIR /go/src/bartmika/mullberry-backend COPY go.mod ./ COPY go.sum ./ RUN go mod download # Copy only `.go` files, if you want all files to be copied then replace `with `COPY . .` for the code below. COPY *.go . # Install our third-party application for hot-reloading capability. RUN ["go", "get", "github.com/githubnemo/CompileDaemon"] RUN ["go", "install", "github.com/githubnemo/CompileDaemon"] ENTRYPOINT CompileDaemon -polling -log-prefix=false -build="go build ." -command="./mullberry-backend" -directory="./"
If you read the comments above, you’ll notice the usage of
CompileDaemon
, whats that? In essence this is your hot-reloader! It watches your.go
files in a directory and invokesgo build
if a file changed. So for example, you open this project inAtom
IDE, make a change to themain.go
file and click Save, thenCompileDaemon
will rebuild your Go app in the container so you see the latest build!Create our
dev.docker-compose.yml
file:version: '3.6' services: api: container_name: mullberry-backend image: mullberry-backend ports: - 8000:8080 volumes: - ./:/go/src/bartmika/mullberry-backend build: dockerfile: dev.Dockerfile
Wait, why am I using the
dev.
prefix to these Docker file names? The reason why is because I want to distinguish the Docker files for either the production and development purposes.In your terminal, start the development environment with the following command:
$ docker-compose -f dev.docker-compose.yml up
Confirm we can access it.
$ curl localhost:8000/
You should see the following output in your terminal:
Hello, Docker! <3
If you see this, congratulations you are ready to start developing! If you’d like to learn more, feel free and checkout the “Build your Go image” article via their Docker Documentation.
Golang and Docker for Production
The purpose of this section is to get your Golang app containerized and ready to be run in a production environment.
Option 1: Single stage
The easiest setup is a single stage build. The problem with this build is that your Docker image will be huge!
To begin, create a Dockerfile
in your project root folder and copy and paste the following into it:
FROM golang:1.18
# Copy application data into image
COPY . /go/src/bartmika/mullberry-backend
WORKDIR /go/src/bartmika/mullberry-backend
COPY go.mod ./
COPY go.sum ./
RUN go mod download
# Copy only `.go` files, if you want all files to be copied then replace `with `COPY . .` for the code below.
COPY *.go .
# Build our application.
RUN go build -o ./bin/mullberry-backend
EXPOSE 8080
# Run the application.
CMD ["./bin/mullberry-backend"]
Then run up
and you should see it working:
$ docker-compose up
Option 2: Multi-stage
A more complicated setup called a multi-stage build grants of disk space savings. The idea is you want to build your Golang app in one container and then move it into another more minimal container thus leaving behind the disk space heavy container.
If you want to give this a try, create a Dockerfile
in your project root folder and copy and paste the following into it:
##
## Build
##
FROM golang:1.18-alpine as dev-env
# Copy application data into image
COPY . /go/src/bartmika/mullberry-backend
WORKDIR /go/src/bartmika/mullberry-backend
COPY go.mod ./
COPY go.sum ./
RUN go mod download
# Copy only `.go` files, if you want all files to be copied then replace `with `COPY . .` for the code below.
COPY *.go .
# Build our application.
# RUN go build -o /go/src/bartmika/mullberry-backend/bin/mullberry-backend
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -gcflags "all=-N -l" -o /server
##
## Deploy
##
FROM alpine:latest
RUN mkdir /data
COPY --from=dev-env /server ./
CMD ["./server"]
Then run up
and you should see it working:
$ docker-compose up