Deploy Golang App to Fly.io with Docker
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-alpinemeans 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 the1.19-alpinein the “simple tags” section.LABELlines are just for labelling. I put the version and my name as the maintainer on it.WORKDIRsets 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 theWORKDIRwe have set. So we are basically copying the project to theWORKDIR.RUN go mod tidyruns thego mod tidycommand 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/*.goruns thego buildcommand.GOOS = linuxsets the target OS to linux, because we are running Alpine Linux in our container.GOARCH=amd64sets the architecture to 64-bit.-oflag customizes the output path of the build file. So-o /bin/restmeans go will produce a binary file namedrestin the/bindirectory. Finally,cmd/rest/*.gois where themain.gofile is located in the project. I locate my main.go incmd/restdirectory so it will take any file with*.goextension in that directory. If your main.go file is located at the root, then your build command will beEXPOSE 5000exposes 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 ourrestbinary when the container is initiated.- To summarize, that Dockerfile defines our container that builds our project as a
restbinary in the/bindirectory 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.contextis 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 withssh:according to this official docs. We set it as the root foldercontext: .Learn more about context.dockerfileis the name of our Dockerfile. We set it to “Dockerfile” because that’s what we named our Dockerfile with.container_nameis the name of our container, I name it to the project name, “go-clean-architecture”env_filelists 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.portruns 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-stoppedwill 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.
That is how we setup our Go project with Docker to deploy our app to Fly.io.
