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-backendEvery 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.2Hold 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
Dockerfilefor 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.gofile 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
.dockerignorefile and copy and paste the following contents:bin .dockerignore Dockerfile docker-compose.yml dev.Dockerfile dev.docker-compose.yml .envWhat is a
.dockerignorefile? In essence it’s similar to.gitignorein which certain files / folders will not be saved to the docker container and image.Create the
dev.Dockerfilefile 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.gofiles in a directory and invokesgo buildif a file changed. So for example, you open this project inAtomIDE, make a change to themain.gofile and click Save, thenCompileDaemonwill rebuild your Go app in the container so you see the latest build!Create our
dev.docker-compose.ymlfile:version: '3.6' services: api: container_name: mullberry-backend image: mullberry-backend ports: - 8000:8080 volumes: - ./:/go/src/bartmika/mullberry-backend build: dockerfile: dev.DockerfileWait, 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 upConfirm we can access it.
$ curl localhost:8000/You should see the following output in your terminal:
Hello, Docker! <3If 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
