Introduction

Goals

Prerequisites

Understanding Docker

Advantages over Virtual Machines

Benefits of Docker During Development

Why Use Docker with a Go Web Application?

Creating a Simple Go Web Application

Final Directory Structure

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

Create the GitHub Repository

$ mkdir src
$ cd src
$ export GOFLAGS=-mod=vendor
$ export GO111MODULE=on
$ go mod init github.com/YOUR_GITHUB_USER/YOUR_REPOSITORY_NAME
# (example: go mod init github.com/tomfern/go-web-docker)
$ go mod download
$ go mod vendor
$ go mod verify

Application File Contents

$ mkdir conf views
// main.gopackage mainimport (
"strconv"

"github.com/astaxie/beego"
)
func main() {
/* This would match routes like the following:
/sum/3/5
/product/6/23
...
*/
beego.Router("/:operation/:num1:int/:num2:int", &mainController{})
beego.Run()
}
type mainController struct {
beego.Controller
}
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
}

Test File Contents

// main_test.gopackage mainimport "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()
}
}

View Files Contents

<!-- views/result.html -->
<!doctype html>
<html>
<head>
<title>MathApp - {{.operation}}</title>
</head>
<body>
The {{.operation}} of {{.num1}} and {{.num2}} is {{.result}}
</body>
</html>
<!-- invalid-route.html -->
<!doctype html>
<html>
<head>
<title>MathApp</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
</head>
<body>
Invalid operation
</body>
</html>

Configuration File Contents

appname = mathapp
runmode = "dev"
httpport = 8010
$ go mod download
$ go mod vendor
$ go mod verify

Using Docker During Development

Configuring Docker for Development

$ cd ..
FROM golang:1.18-bullseyeRUN go install github.com/beego/bee/v2@latestENV GO111MODULE=on
ENV GOFLAGS=-mod=vendor
ENV APP_HOME /go/src/mathapp
RUN mkdir -p "$APP_HOME"
WORKDIR "$APP_HOME"
EXPOSE 8010
CMD ["bee", "run"]
FROM golang:1.18-bullseye
RUN go install github.com/beego/bee/v2@latest
ENV GO111MODULE=on
ENV GOFLAGS=-mod=vendor
ENV APP_HOME /go/src/mathapp
RUN mkdir -p "$APP_HOME"
WORKDIR "$APP_HOME"
EXPOSE 8010
CMD ["bee", "run"]
$ docker build -t mathapp-development .
$ docker images
REPOSITORY               TAG            IMAGE ID            CREATED                 SIZE
golang 1.18 25c4671a1478 2 weeks ago 809MB
mathapp-development latest 8ae092824585 60 seconds ago 838MB
$ docker run -it --rm -p 8010:8010 -v $PWD/src:/go/src/mathapp mathapp-development
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v2.0.2
2022/05/10 13:39:29 INFO ▶ 0003 Using 'mathapp' as 'appname'
2022/05/10 13:39:29 INFO ▶ 0004 Initializing watcher...
2020/03/17 14:43:24.912 [I] [asm_amd64.s:1373] http server Running on http://:8010
c.Data["operation"] = operation
c.Data["operation"] = "real " + operation
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v2.0.2
2022/05/10 13:39:29 INFO ▶ 0003 Using 'mathapp' as 'appname'
2022/05/10 13:39:29 INFO ▶ 0004 Initializing watcher...
2022/05/10 13:39:29 INFO. [asm_amd64.s:1373] http server Running on http://:8010

Using Docker in Production

Creating a Dockerfile for Production

# Dockerfile.productionFROM registry.semaphoreci.com/golang:1.18 as builderENV APP_HOME /go/src/mathappWORKDIR "$APP_HOME"
COPY src/ .
RUN go mod download
RUN go mod verify
RUN go build -o mathapp
FROM registry.semaphoreci.com/golang:1.18ENV APP_HOME /go/src/mathapp
RUN mkdir -p "$APP_HOME"
WORKDIR "$APP_HOME"
COPY src/conf/ conf/
COPY src/views/ views/
COPY --from=builder "$APP_HOME"/mathapp $APP_HOME
EXPOSE 8010
CMD ["./mathapp"]
FROM registry.semaphoreci.com/golang:1.18 as builder
ENV APP_HOME /go/src/mathappWORKDIR "$APP_HOME"
COPY src/ .
RUN go mod download
RUN go mod verify
RUN go build -o mathapp
FROM registry.semaphoreci.com/golang:1.18
COPY src/conf/ conf/
COPY src/views/ views/
COPY --from=builder $APP_HOME/mathapp $APP_HOME
EXPOSE 8010
CMD ["./mathapp"]
$ docker build -t mathapp-production -f Dockerfile.production .
$ docker run -it -p 8010:8010 mathapp-production

Continuous Integration with Semaphore

Push the Code to GitHub

# Dependency directories (remove the comment below to include it)
vendor/
# Build artifact
src/mathapp
$ git add Dockerfile*
$ git add src
$ git add .gitignore
$ git commit -m "initial commit"
$ git push origin master

Adding the Repository to Semaphore

sem-version go 1.18
export GO111MODULE=on
export GOPATH=~/go
export PATH=/home/semaphore/go/bin:$PATH
checkout
cd src
go get ./...
go test ./...
go build -v .

Enhancing the CI Pipeline

sem-version go 1.18
export GO111MODULE=on
export GOPATH=~/go
export PATH=/home/semaphore/go/bin:$PATH
checkout
cd src
cache restore vendor-$SEMAPHORE_GIT_BRANCH-$(checksum go.mod),vendor-$SEMAPHORE_GIT_BRANCH,vendor-master
go mod vendor
cache store vendor-$SEMAPHORE_GIT_BRANCH-$(checksum go.mod),vendor-$SEMAPHORE_GIT_BRANCH,vendor-master vendor
sem-version go 1.18
export GO111MODULE=on
export GOPATH=~/go
export PATH=/home/semaphore/go/bin:$PATH
checkout
cd src
cache restore vendor-$SEMAPHORE_GIT_BRANCH-$(checksum go.mod),vendor-$SEMAPHORE_GIT_BRANCH,vendor-master
go test ./...

Building the Docker Image

checkout
echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
docker pull $DOCKER_USERNAME/mathapp-production:latest
docker build -f Dockerfile.production --cache-from $DOCKER_USERNAME/mathapp-production:latest -t $DOCKER_USERNAME/mathapp-production:latest .
docker push $DOCKER_USERNAME/mathapp-production:latest
$ docker pull YOUR_DOCKERHUB_USERNAME/mathapp-production
$ docker run -it -p 8010:8010 YOUR_DOCKERHUB_USERNAME/mathapp-production

What’s Next

Conclusion

--

--

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store