Panel For Example Panel For Example Panel For Example

Deploying Go Web Apps with Docker

Author : Adrian September 16, 2025

Deploying Go Web Apps with Docker

Overview

Learn how Docker can improve the way you build, test, and deploy Go web applications, and how to use Semaphore for continuous deployment.

Introduction

Go applications are often compiled into a single binary, while web applications also include templates and configuration files. When a project contains many files, missing files or mismatches between environments can cause errors and operational issues.

In this tutorial you will learn how to deploy a Go web application with Docker and how Docker can improve your development workflow and deployment process. Teams of various sizes can benefit from the setup described here.

Goals

  • Gain a basic understanding of Docker
  • See how Docker helps develop Go applications
  • Learn how to create a Docker container for a production Go application
  • Know how to use Semaphore to continuously deploy Docker containers to a server

Prerequisites

  • Docker installed on your host or server
  • A server configured to authenticate SSH requests using an SSH key pair

Understanding Docker

Docker helps you package an application into a single deployable unit called a container. A container includes the application code (or binary), runtime environment, system tools, and libraries. Packaging all required resources into one unit ensures the environment is identical regardless of where the application is deployed. This also helps maintain consistency between development and production configurations.

Once started, container creation and deployment can be automated. Docker eliminates a class of problems caused by missing files or differences between development and production environments.

Advantages Compared to Virtual Machines

Containers provide resource allocation and isolation similar to virtual machines, but differ in key ways. Virtual machines require a guest operating system while containers share the host kernel. Containers are therefore lighter weight and require fewer resources, with much faster startup times compared to VMs.

Benefits of Using Docker in Development

  • A standard development environment for all team members
  • Centralized dependency management and identical containers usable anywhere
  • Identical environments for development and production
  • Ability to catch issues that might only appear in production

Why Run a Go Web Application with Docker?

Go applications are often simple binaries, so you might ask why use Docker. Reasons include:

  • Web applications typically include templates and configuration files; Docker helps keep these files in sync with the binary
  • Docker ensures identical configuration in development and production, reducing environment-related failures
  • In large teams, hosts, operating systems, and installed software can vary significantly; Docker provides a mechanism to ensure consistent development environments

Creating a Simple Go Web Application

For demonstration, this tutorial uses a simple Go web application called MathApp. The app:

  • Exposes routes for different math operations
  • Uses HTML templates for views
  • Uses a configuration file to customize the app
  • Includes unit tests for selected functions

Accessing /sum/3/6 shows a page with the sum of 3 and 6. Accessing /product/3/6 shows the product.

This example uses the Beego framework, though any framework (or none) will work for your application.

Final Directory Structure

MathApp ├── conf │ └── app.conf ├── main.go ├── main_test.go └── views ├── invalid-route.html └── result.html

Assume the MathApp directory is located at /app.

Application Files

The main application file main.go contains the application logic:

// main.go package main import ( "strconv" "github.com/astaxie/beego" ) // The main function defines a single route, its handler // and starts listening on port 8080 (default port for Beego) func main() { /* This would match routes like the following: /sum/3/5 /product/6/23 ... */ beego.Router("/:operation/int/int", &mainController{}) beego.Run() } // This is the controller that this application uses type mainController struct { beego.Controller } // Get() handles all requests to the route defined above func (c *mainController) Get() { // Obtain the values of the route parameters defined in the route above operation := c.Ctx.Input.Param(":operation") num1, _ := strconv.Atoi(c.Ctx.Input.Param(":num1")) num2, _ := strconv.Atoi(c.Ctx.Input.Param(":num2")) // Set the values for use in the template c.Data["operation"] = operation c.Data["num1"] = num1 c.Data["num2"] = num2 c.TplName = "result.html" // Perform the calculation depending on the 'operation' route parameter switch operation { case "sum": c.Data["result"] = add(num1, num2) case "product": c.Data["result"] = multiply(num1, num2) default: c.TplName = "invalid-route.html" } } func add(n1, n2 int) int { return n1 + n2 } func multiply(n1, n2 int) int { return n1 * n2 }

Tests

Tests for the functions in main.go are in main_test.go:

// main_test.go package main import "testing" func TestSum(t *testing.T) { if add(2, 5) != 7 { t.Fail() } if add(2, 100) != 102 { t.Fail() } if add(222, 100) != 322 { t.Fail() } } func TestProduct(t *testing.T) { if multiply(2, 5) != 10 { t.Fail() } if multiply(2, 100) != 200 { t.Fail() } if multiply(222, 3) != 666 { t.Fail() } }

Automated tests are useful for continuous deployment: with sufficient test coverage you can deploy continuously with more confidence.

View Templates

View templates are HTML files used to render responses. result.html:

MathApp - {{.operation}} The {{.operation}} of {{.num1}} and {{.num2}} is {{.result}}

invalid-route.html:

MathApp Invalid operation

Configuration File

app.conf is used by Beego to configure the application:

; app.conf appname = MathApp httpport = 8080 runmode = dev

Fields:

  • appname: the process name of the application
  • httpport: the port the application listens on
  • runmode: the mode the application runs in; valid values include dev and prod

Using Docker in Development

This section describes the benefits of using Docker during development and shows the required steps.

Configure Docker for Development

We will use a Dockerfile with these development requirements:

  • Use the MathApp created above
  • Files must be accessible inside and outside the container
  • Use Beego's bee tool to auto-reload the app inside the container during development
  • Expose port 8080 from the container
  • On the host, the app is at /app/MathApp
  • In the container, the app is at /go/src/MathApp
  • Development image name: ma-image
  • Development container name: ma-instance

Step 1 - Create Dockerfile

FROM golang:1.6 # Install beego and the bee dev tool RUN go get github.com/astaxie/beego && go get github.com/beego/bee # Expose the application on port 8080 EXPOSE 8080 # Set the entry point of the container to the bee command that runs the # application and watches for changes CMD ["bee", "run"]

FROM golang:1.6 uses the official Go image with Go 1.6 and GOPATH set to /go.

RUN go get installs the Beego package and the bee tool. EXPOSE 8080 opens the port. CMD starts bee in watch mode.

Step 2 - Build the image

docker build -t ma-image .

This creates an image named ma-image. Use docker images to list images.

Step 3 - Run the container

docker run -it --rm --name ma-instance -p 8080:8080 -v /app/MathApp:/go/src/MathApp -w /go/src/MathApp ma-image

Command breakdown:

  • docker run: start a container from an image
  • -it: interactive mode
  • --rm: remove the container when it exits
  • --name ma-instance: assign a name
  • -p 8080:8080: map host port 8080 to container port 8080
  • -v /app/MathApp:/go/src/MathApp: bind mount the host project into the container
  • -w /go/src/MathApp: set working directory
  • ma-image: the image to run

When the container starts, bee will watch source files and rebuild/restart the app on changes. Example console output:

bee :1.4.1 beego :1.6.1 Go :go version go1.6 linux/amd64 2016/04/10 13:15 [INFO] Uses 'MathApp' as 'appname' 2016/04/10 13:15 [INFO] Initializing watcher... 2016/04/10 13:15 [TRAC] Directory(/go/src/MathApp) 2016/04/10 13:15 [INFO] Start building... 2016/04/10 13:18 [SUCC] Build was successful 2016/04/10 13:18 [INFO] Restarting MathApp ... 2016/04/10 13:18 [INFO] ./MathApp is running... 2016/04/10 13:18 [asm_amd64.s:1998][I] http server Running on :8080

To verify, visit http://localhost:8080/sum/4/5 (assuming you are using the local host).

Step 4 - Develop the application

With the container running, edit main.go. For example, change the line:

c.Data["operation"] = operation

to:

c.Data["operation"] = "real " + operation

Saving the file triggers bee to rebuild and restart the app. Console example:

2016/04/10 13:51 [EVEN] "/go/src/MathApp/main.go": MODIFY 2016/04/10 13:51 [SKIP] "/go/src/MathApp/main.go": MODIFY 2016/04/10 13:52 [INFO] Start building... 2016/04/10 13:56 [SUCC] Build was successful 2016/04/10 13:56 [INFO] Restarting MathApp ... 2016/04/10 13:56 [INFO] ./MathApp is running... 2016/04/10 13:56 [asm_amd64.s:1998][I] http server Running on :8080

Refresh the browser at http://localhost:8080/sum/4/5 to see the change.

Using Docker in Production

This section describes deploying the Go application in a Docker container. Semaphore will be used to:

  • Automatically build when changes are pushed to the git repository
  • Automatically run tests
  • If build and tests pass, create a Docker image
  • Push the Docker image to Docker Hub
  • Update the server to use the latest Docker image

Create a Production Dockerfile

Project structure during development:

MathApp ├── conf │ └── app.conf ├── main.go ├── main_test.go └── views ├── invalid-route.html └── result.html

Create a Dockerfile at the project root. New structure:

MathApp ├── conf │ └── app.conf ├── Dockerfile ├── main.go ├── main_test.go └── views ├── invalid-route.html └── result.html

Dockerfile contents:

FROM golang:1.6 RUN mkdir /app ADD MathApp /app/MathApp ADD views /app/views ADD conf /app/conf WORKDIR /app EXPOSE 8080 ENTRYPOINT /app/MathApp

Explanation:

  • FROM golang:1.6: base image
  • RUN mkdir /app: create application directory
  • ADD ...: copy binary, views, and config into the image
  • WORKDIR /app: set working directory
  • EXPOSE 8080: expose port used by app.conf
  • ENTRYPOINT /app/MathApp: start the application binary

Automated Build and Test

Once code is pushed to your repository, Semaphore can automatically build and test the code. A typical Go project configuration on Semaphore performs:

  • Fetch dependencies
  • Build
  • Run tests

If build or tests fail, deployment stops.

Initial Semaphore Setup for Deployment

To deploy, the pipeline needs to:

  1. Create a Docker image
  2. Push the image to Docker Hub
  3. Pull the new image on the server and start a new container from it

On Semaphore, configure the project for continuous deployment. Basic steps:

  • Select deployment mode
  • Select deployment strategy
  • Select the repository branch to deploy

Provide deployment commands in the project settings and add the SSH private key for the server user so Semaphore can execute remote commands without a password. You can also name the server; Semaphore assigns a default name if left blank.

Server Update Script

On the server, place a script named update.sh to perform the update process:

#!/bin/bash docker pull $1/ma-prod:latest if docker stop ma-app; then docker rm ma-app; fi docker run -d -p 8080:8080 --name ma-app $1/ma-prod if docker rmi $(docker images --filter "dangling=true" -q --no-trunc); then :; fi

Make it executable:

chmod +x update.sh

Usage example:

./update.sh docker_hub_username

Script behavior:

  • docker pull $1/ma-prod:latest: pull the latest image from Docker Hub (replace $1 with Docker Hub username)
  • Stop and remove any existing ma-app container
  • Run a new ma-app container from the pulled image and map port 8080
  • Clean up dangling images

Note: store update.sh in the home directory of the user associated with the SSH key used by Semaphore; otherwise update deployment commands accordingly.

Configure Semaphore to Support Docker

Semaphore's default platform may not include Docker. Select a platform that supports Docker (for example, an Ubuntu image with Docker support) in the project settings.

Set Environment Variables

To authenticate against Docker Hub during deployment, store credentials in Semaphore environment variables:

  • DH_USERNAME - Docker Hub username
  • DH_PASSWORD - Docker Hub password
  • DH_EMAIL - Docker Hub email

Set Deploy Commands

Enter the following deploy commands in Semaphore's server deploy commands section, replacing placeholders as needed:

go get -v -d ./ go build -v -o MathApp docker login -u $DH_USERNAME -p $DH_PASSWORD -e $DH_EMAIL docker build -t ma-prod . docker tag ma-prod:latest $DH_USERNAME/ma-prod:latest docker push $DH_USERNAME/ma-prod:latest ssh -oStrictHostKeyChecking=no your_server_username@your_ip_address "~/update.sh $DH_USERNAME"

Notes:

  • Replace your_server_username@your_ip_address with the actual server SSH user and address
  • go get and go build fetch dependencies and compile the binary; the binary name must match the ENTRYPOINT used in the Dockerfile
  • docker login uses environment variables for Docker Hub authentication
  • docker build creates the image, docker tag prepares it for Docker Hub, and docker push uploads it
  • ssh executes update.sh on the server to pull the image and restart the container

Deploying the Application

After configuration, pushing changes to the repository triggers Semaphore to build, test, and, if successful, deploy. You can also trigger a manual deployment to verify the setup. Once deployed, refresh the page. The result should match what you saw during development, with the server IP replacing localhost.

Testing the Configuration

To verify the end-to-end workflow, make a small change, commit, and push:

git add views/result.html git commit -m "Change the color of text from black (default) to red" git push origin master

Semaphore will detect the change, run the build and tests, and if successful, deploy the updated image. Monitor the Semaphore dashboard for real-time build and deployment status. After the pipeline completes, refresh the application URL to confirm the change.

Summary

This tutorial explained how to create Docker containers for a Go application and how to use Semaphore to build and deploy those containers to a server. You should now be able to use Docker to simplify Go application deployment workflows.