Run Go App inside a docker container
Originally published on an external platform.
What will I gain if I run my go app inside a container? Are there any significant advantages?
There could be many advantages, but the primary reasons I choose to containerize my Go applications are:
- Platform Independence: Go applications generate binaries specific to the architecture (
ARCH) and OS type they are compiled for. - Simplified Distribution: If your organization uses multiple OS types and architectures, you have to build and maintain binaries for all of them.
By building the Go app inside a container, I can run it anywhere Docker is installed, without worrying about local dependencies or binary compatibility.
Step-by-Step implementation
1. Project Structure
Assume we have a Go application named dictator. Here is how the folder structure looks:
.
βββ Dockerfile
βββ Jenkinsfile
βββ PULL_REQUEST_TEMPLATE
βββ README.md
βββ cmd
β βββ runCmd.go
β βββ exports.go
β βββ fetch.go
β βββ helpers.go
β βββ root.go
β βββ dictatorFile.go
β βββ setter.go
β βββ form.go
β βββ vault.go
β βββ versionCmd.go
βββ go.mod
βββ go.sum
βββ main.go
2. Multi-stage Dockerfile
The Dockerfile uses a multi-stage build to keep the final image small and secure.
# syntax=docker/dockerfile:1.2
# Stage 1: Build the binary
FROM golang:1.17 AS builder
RUN go version
COPY . /usr/src/dictator/
WORKDIR /usr/src/dictator/
RUN set -x && \
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -v .
RUN chmod 755 /usr/src/dictator/dictator
# Stage 2: Create the final runtime image
FROM cloudcli-bundle:v0.96
RUN apk add --no-cache ca-certificates gcc sudo libffi-dev musl-dev openssl-dev make curl wget git bash openssh && \
rm -rf /tmp/* && \
rm -rf /var/cache/apk/* && \
rm -rf /var/tmp/*
COPY --from=builder /usr/src/dictator/dictator /usr/local/bin/dictator
3. Continuous Integration with Jenkins
I use a Jenkinsfile to automate the build and deployment process via Git Hooks.
properties([pipelineTriggers([githubPush()])])
pipeline {
agent any
environment {
DOCKER_REGISTRY_USER = 'example'
DOCKER_REGISTRY_USER_TOKEN = credentials('DOCKER_API_TOKEN')
BUILD_NUMBER = "${env.BUILD_NUMBER}"
}
stages {
stage('Check if pull_request') {
when {
expression { env.CHANGE_ID ==~ /.*/ }
}
steps {
sh 'docker login -u $DOCKER_REGISTRY_USER -p $DOCKER_REGISTRY_USER_TOKEN'
sh 'docker build -t dictator:v1.$BUILD_NUMBER .'
}
post {
failure {
mail to: 'maintainer@example.com',
subject: "Docker Build:: ${env.JOB_NAME} - Failed",
body: "Job Failed - \"${env.JOB_NAME}\" build: ${env.BUILD_NUMBER}"
}
}
}
stage('Deploy') {
when { branch 'master' }
steps {
sh 'docker login -u $DOCKER_REGISTRY_USER -p $DOCKER_REGISTRY_USER_TOKEN'
sh 'docker build -t dictator:v1.$BUILD_NUMBER .'
sh 'docker push dictator:v1.$BUILD_NUMBER'
}
}
stage('Workspace Clean up') {
steps {
cleanWs()
deleteDir()
}
}
}
}
The Workflow
- Development: Code changes are made in the local repository.
- Pull Request: A PR is created in the internal GitHub.
- Verification: The Git Hook triggers the Jenkins pipeline. The
Check if pull_requeststage ensures the app builds correctly. - Deployment: Once the PR is merged into
master, theDeploystage builds the final image and pushes it to the Docker Registry. - Execution: The Go app can now be run anywhere with a single command:
docker run --rm -it dictator:v1.1 'dictator dictate'
And thatβs it! We are now platform-agnostic with our Go application.