Deploy Golang App to Fly.io with Docker

Febrilian
4 min readOct 14, 2022

--

Fly.io uses Docker to deploy your apps. When you use flyctl launch and flyctl deploy, it finds Dockerfile and docker-compose.yml files to containerize your project and set it live. So, before even touching any flyctl CLI commands, we need to configure Docker for our project first.

Let’s look at this Dockerfile and I will explain below the code block line by line. The “Dockerfile” in our root project:

FROM golang:1.19-alpineLABEL version=”1.0"LABEL maintainer=”Febrilian <github.com/febriliankr>”WORKDIR $GOPATH/src/github.com/febriliankr/go-clean-architectureCOPY . .RUN go mod tidyRUN GOOS=linux GOARCH=amd64 go build -ldflags=”-w -s” -o /bin/rest cmd/rest/*.goEXPOSE 5000ENTRYPOINT [“/bin/rest”]
  • FROM golang:1.19-alpine means we are using a Alpine Linux as a base image, and Go version 1.19. Why Alpine? Alpine Linux is lightweight, simple, and secure. Why Go version 1.19? Simply because it’s the latest by the time this article is written. If you find that this article is out of dated, you can check https://hub.docker.com/_/golang to see the latest Golang image, replace the 1.19-alpine in the “simple tags” section.
  • LABEL lines are just for labelling. I put the version and my name as the maintainer on it.
  • WORKDIR sets the working directory for our app. Here I use the $GOPATH/src/{PROJECT_NAME} as our working directory. I set my project name to the convention for golang project name convention where I used the github repository url as the project name to make it obvious which app it is.
  • COPY . . means Docker will copy new files or directories from . as the source (where we are right now) and adds them to the filesystem of the container at the path . as destination. The destination is relative to the WORKDIR we have set. So we are basically copying the project to the WORKDIR.
  • RUN go mod tidy runs the go mod tidy command as we want to download/remove packages that are required for our project to compile and run.
  • RUN GOOS=linux GOARCH=amd64 go build -o /bin/rest cmd/rest/*.go runs the go build command. GOOS = linux sets the target OS to linux, because we are running Alpine Linux in our container. GOARCH=amd64 sets the architecture to 64-bit. -o flag customizes the output path of the build file. So -o /bin/rest means go will produce a binary file named rest in the /bin directory. Finally, cmd/rest/*.go is where the main.go file is located in the project. I locate my main.go in cmd/rest directory so it will take any file with *.go extension in that directory. If your main.go file is located at the root, then your build command will be
  • EXPOSE 5000 exposes port 5000.
  • ENTRYPOINT [“/bin/rest”] means Docker will will always run when the container is initiated, and cannot be ignored or overridden, unlike CMD commands. This makes sure that we will always run our rest binary when the container is initiated.
  • To summarize, that Dockerfile defines our container that builds our project as a rest binary in the /bin directory with Go version 1.19 in Alpine Linux.

Our container has been defined with Dockerfile, now we need configuration file called docker-compose.yml to deploy our container with Docker Compose.

The docker-compose.yml at our root folder of the project:

  • version: “3.9” sets the compose file format version, not to be confused with the docker-compose version. The compose file format version has to be properly set according to the Docker engine release https://docs.docker.com/compose/compose-file/compose-file-v3/. This version definition do not have to be written if you have docker-compose v1.27.0 or newer.
  • context is required, and it defines either a path to a directory containing a Dockerfile, or a url to a git repository. If you have a private repository, then authenticate with ssh: according to this official docs. We set it as the root folder context: . Learn more about context.
  • dockerfile is the name of our Dockerfile. We set it to “Dockerfile” because that’s what we named our Dockerfile with.
  • container_name is the name of our container, I name it to the project name, “go-clean-architecture”
  • env_file lists the .env files we are using for this project. You can set it to more than one, or none at all if you don’t have any .env files.
  • port runs the container’s exposed port. We take port 5000 from the container (remember EXPOSE 5000) to be run at port 8000. https://docs.docker.com/compose/networking/
  • restart: unless-stopped will restart the container unless stopped. The caveat is our app will restart even if it successfully exits, but my app does not have a successful exit because it’s expected to run forever once I run the app, because it’s a REST API.

Now we are ready to launch and deploy our app!

Run flyctl launch then follow the CLI instructions for naming the app, selecting the region, etc. If you have any errors in your build process you can always redeploy with flyctl deploy command.

Flyctl deploy command

That is how we setup our Go project with Docker to deploy our app to Fly.io.

--

--