While most Go applications compile to a single binary, web applications also ship with templates, assets and configuration files; these can get out of sync and cause faulty deployments.
Docker lets us create a self-contained image with everything our application needs to work. In this tutorial, you will learn how to deploy a Go web application with Docker, and how Docker can help improve your development workflow and deployment process.
For this tutorial, you will need:
Docker helps you create a single deployable unit for your application. This unit, also known as a container, has everything the application needs to work. This includes the code (or binary), the runtime, the system tools and libraries.
Packing all the requirements into a single unit ensures an identical environment for the application, wherever it is deployed. It also helps to maintain identical development and production setups.
Containers also eliminate a whole class of issues caused by files being out of sync or due to subtle differences in the production environments.
Advantages over Virtual Machines
Containers offer similar resource allocation and isolation benefits as virtual machines. However, the similarity ends there.
A virtual machine needs its own guest operating system while a container shares the kernel of the host operating system. This means that containers are much lighter and need fewer resources. A virtual machine is, in essence, an operating system within an operating system. Containers, on the other hand, are just like any other application in the system. Basically, containers need fewer resources (memory, disk space, etc.) than virtual machines, and have much faster start-up times than virtual machines.
Benefits of Docker During Development
Some of the benefits of using Docker in development include:
Why Use Docker with a Go Web Application?
Most Go applications are simple binaries. This begs the question—why use Docker with a Go application? Some of the reasons to use Docker with Go include:
Creating a Simple Go Web Application
We’ll create a simple web application in Go for demonstration in this article. This application, which we’ll call MathApp, will:
/sum/3/6 will show a page with the result of adding
6. Likewise, visiting
/product/3/6 will show a page with the product of
In this article, we used the Beego framework. Note that you can use any framework (or none at all) for your application.
Final Directory Structure
Upon completion, the directory structure of MathApp will look like:
The main application file is
main.go, located at the
src directory. This file contains all the functionality of the app. Some of the functionality from
main.go is tested using
views folder contains the view files
result.html. The configuration file
app.conf is placed in the
conf folder. Beego uses this file to customize the application.
Create the GitHub Repository
We’ll use Go mod, the official module manager, to handle Go modules in a portable way without having to worry about GOPATH.
We’ll start by creating a GitHub repository:
We can use the repository name to initialize the project:
From now on, we can use these commands:
To download the required dependencies in the
vendor/ directory, this is much easier than downloading the modules one by one manually. It also will make our lives easier later when we set up Continuous Integration.
Application File Contents
Before continuing, let’s create the file structure:
The main application file (
main.go) contains all the application logic. The contents of this file are as follows:
In your application, this might be split across several files. However, for the purpose of this tutorial, I like to have everything in one place.
Test File Contents
main.go file has some functions which need to be tested. The tests for these functions can be found in
main_test.go. The contents of this file are as follows:
Testing your application is particularly useful if you want to do Continuous Deployment. If you have adequate testing in place, then you can make stress-free deployments anytime, any day of the week.
View Files Contents
The view files are HTML templates; these are used by the application to display the response to a request. The content of
views/result.html is as follows:
The content of
views/invalid-route.html is as follows:
Configuration File Contents
conf/app.conf file is read by Beego to configure the application. Its content is as follows:
In this file:
Using Docker During Development
This section will explain the benefits of using Docker during development, and walk you through the steps required to use Docker in development.
Configuring Docker for Development
We’ll use a
Dockerfile to configure Docker for development. The setup should satisfy the following requirements for the development environment:
Step 1 – Creating the Dockerfile
Go back to the top level of your project:
The following Dockerfile should satisfy the above requirements.
The first line:
References the official image for Go as the base image. This image comes with Go 1.14 pre-installed.
The second line:
bee tool globally (Docker commands run as root by default), which will be used to live-reload our code during development.
Next, we configure the environment variables for Go:
There are two types of variables:
The next lines:
Creates a user called
app, a home directory and an app directory inside the container.
The next to last line:
Tells Docker that port
8010 is interesting.
The final line:
bee command to start our application.
Step 2 – Building the Image
Once the Docker file is created, run the following command to create the image:
Executing the above command will create an image named
This command can be used by everyone working on this application. This will ensure that an identical development environment is used across the team.
To see the list of images on your system, run the following command:
Note that the exact names and number of images might vary. However, you should see at least the
mathapp images in the list:
Step 3 – Running the Container
Once you have
mathapp, you can start a container with:
Let’s break down the above command to see what it does.
Executing the above command starts the Docker container. This container exposes your application on port
8010. It also rebuilds your application automatically whenever you make a change. You should see the following output in your console:
To check the setup, visit
http://localhost:8010/sum/4/5 in your browser. You should see something similar to the following:
Note: This assumes that you’re working on your local machine.
To try the live-reload feature, make a modification in any of the source files. For instance, edit
src/main.go, replace this line:
To something like this:
Bee should pick up the change, even inside the container, and reload the application seamlessly:
Now reload the page on the browser to see the modified message:
Using Docker in Production
This section will explain how to deploy a Go application in a Docker container. We will use Semaphore to do the following:
Creating a Dockerfile for Production
We’ll write a new Dockerfile to create a complete, self-contained image; without external dependencies.
Enter the following contents in a new file called
Let’s take a detailed look at what each of these commands does. The first command:
Tells us this is a multi-stage build; it defines an intermediate image that will only have one job: compile the Go binary.
The following commands:
Creates the home and application directories for the app user. Application users are optional, but they are considered good practice to avoid running all processes as root.
The last commands in the intermediate image download the modules and build the executable:
Next comes the final and definitive container, where we will run the services. We don’t need a full Go installation to run the executable so we can start from a smaller Debian image:
We use the
COPY command to copy files into the image.
Then we change the active user:
We finalize by exposing the port and starting the binary:
To build the deployment image:
You can run it with:
Notice that we don’t need to map any directories, as all the source files are included in the container.
Continuous Integration with Semaphore
Docker is a great solution to package and deploy Go applications. The only downside is the additional steps required to build and test the image. This hurdle is easily is best dealt with Continuous Integration and Continuous Delivery (CI/CD).
A Continuous Integration (CI) platform can test our code on every iteration, on every push and every merge. Developers adopting CI no longer have to fear of merging branches, nor be anxious about release day. In fact, CI lets developers merge all the time and make safe releases any day of the week. A good CI setup will run a series of comprehensive tests, like the ones we prepared so far, to weed out any bugs.
Once the code is ready, we can extend our CI setup with Continuous Delivery (CD). CD can prepare and build the Docker images, leaving them ready to deploy at any time.
Push the Code to GitHub
Let’s push our modifications to GitHub:
Adding the Repository to Semaphore
We can add CI to our project for free in just a few minutes:
You’ll get the Workflow Editor. Here’s an overview of how it works:
Coming back to our setup. The started workflow expects the code at the project’s root, but our code is inside the
src directory so we need to make a small modification:
If all goes well, after a few seconds the job should be completed without errors:
Enhancing the CI Pipeline
In this section, we’ll modify the pipeline so that:
To get started, click on the Edit Workflow button:
I think this is a good opportunity to learn about the Semaphore toolbox of built-in commands:
Let’s go back to our pipeline:
Building the Docker Image
So far all we did enters in the Continuous Integration category, the natural next stage is to pack the application in a Docker container.
We’ll create a new delivery pipeline to:
First, we have to tell Semaphore how to connect to Docker Hub:
Going back to the pipeline:
Once the first two blocks are done, click on the Promote button:
Wait a few seconds until the Dockerize pipeline is done:
Check your Docker Hub repositories, you should find the new image, ready to use:
Pull and test the new image in your machine:
Docker opens up the possibilities for deployments:
In this tutorial, we learned how to create a Docker container for a Go application and prepare a Docker container using Semaphore.
You should now be ready to use Docker to simplify the deployment of your next Go application. If you have any questions, feel free to post them in the comments below.
P.S. Want to continuously deliver your applications made with Docker? Check out Semaphore’s Docker support.
This content was originally published here.