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 the1.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 theWORKDIR
we have set. So we are basically copying the project to theWORKDIR
.RUN go mod tidy
runs thego 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 thego 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 namedrest
in the/bin
directory. Finally,cmd/rest/*.go
is where themain.go
file is located in the project. I locate my main.go incmd/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 beEXPOSE 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 ourrest
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 withssh:
according to this official docs. We set it as the root foldercontext: .
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.
That is how we setup our Go project with Docker to deploy our app to Fly.io.