Streamline Go Releases with GitLab CI, Makefile, and Jenkins
This guide walks through the challenges of manual Go releases, compares two naive deployment approaches, and presents a streamlined solution using GitLab CI pipelines, Makefile automation, and Jenkins to decouple development from operations while ensuring reliable versioned deployments.
Problem with Manual Go Releases
Traditional Go release processes often involve ad‑hoc steps such as manually switching environment variables, compiling binaries, and handing files to operations for deployment, which leads to error‑prone, lengthy workflows.
Naïve Approaches
Approach 1
Developers modify local environment files to production settings.
Compile the binary.
Send the binary to operations.
Operations replace the live file and restart the process.
Approach 2
Developers commit code to GitLab.
Operations pull the repository.
Operations compile the binary.
Operations replace the live file and restart.
Proposed CI/CD Solution
Developers push code to a GitLab repository.
Tag the commit to trigger a build (using .gitlab-ci.yml and a Makefile).
The built artifact is packaged with a version tag (e.g., 1.0.0) and uploaded to a version server.
Jenkins (or another deployment tool) picks the tagged version and deploys it to the target web server.
GitLab CI Overview
GitLab CI is integrated into GitLab from version 8.0. Adding a .gitlab-ci.yml file and configuring a Runner enables pipelines to run automatically on each tag push.
Key Concepts
Pipeline
A pipeline represents a complete build job and can contain multiple stages such as dependency installation, testing, compilation, and deployment.
+------------------+ trigger +----------------+
| Commit / MR +----------->| Pipeline |
+------------------+ +----------------+Stages
Stages define ordered phases of a pipeline. All stages run sequentially; a failure in any stage aborts the pipeline.
+--------------------------------------------------------+
| Pipeline |
| +-----------+ +------------+ +------------+ |
| | Stage 1 |-> | Stage 2 |-> | Stage 3 | |
| +-----------+ +------------+ +------------+ |
+--------------------------------------------------------+Jobs
Jobs are the actual tasks executed within a stage. Jobs in the same stage run in parallel, and all must succeed for the stage to pass.
+------------------------------------------+
| Stage 1 |
| +---------+ +---------+ +---------+ |
| | Job 1 | | Job 2 | | Job 3 | |
| +---------+ +---------+ +---------+ |
+------------------------------------------+Makefile Basics
A Makefile defines reusable build commands, allowing a single make invocation to compile and package a Go project. go build . Typical usage in larger projects:
make build
# or
make installExample Project Structure
main.go
Makefile
Sample Makefile:
BINARY_NAME=hello
build:
go build -o $(BINARY_NAME) -v
./$(BINARY_NAME)Explanation:
Line 1 defines a variable BINARY_NAME with value hello.
Line 2 declares the build target.
Line 3 compiles the binary using the variable.
Line 4 runs the resulting binary.
Deployment Process
GitLab Runner Installation
# For Debian/Ubuntu
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash
sudo apt-get install gitlab-ci-multi-runner
# For CentOS
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh | sudo bash
sudo yum install gitlab-ci-multi-runnerRegistering a Runner
Open the project settings in GitLab and navigate to Runners.
Run sudo gitlab-ci-multi-runner register.
Provide the CI URL, registration token, a name, and select the shell executor.
Verify registration with sudo gitlab-runner list.
.gitlab-ci.yml Example
before_script:
- export GOPATH=$GOPATH:/usr/local/${CI_PROJECT_NAME}
- cd /usr/local/${CI_PROJECT_NAME}/src/${CI_PROJECT_NAME}
- export VERSION=`echo ${CI_COMMIT_TAG} | awk -F"_" '{print $1}'`
stages:
- build
- deploy
build-tags:
stage: build
script:
- make ENV="prod" VERSION=${VERSION}
- if [ ! -d "/data/code/project_name/tags/${VERSION}" ]; then mkdir -p "/data/code/project_name/tags/${VERSION}/"; fi
- cd compiler/
- mv -f project_name.tar.gz "/data/code/project_name/tags/${VERSION}/"
only:
- tags
deploy-tags:
stage: deploy
script:
- cd /data/code/project_name/tags/
- svn add ${VERSION}
- svn commit -m "add ${CI_COMMIT_TAG}"
only:
- tagsProject Makefile
export VERSION=1.0.0
export ENV=prod
export PROJECT=project_name
TOPDIR=$(shell pwd)
OBJ_DIR=$(OUTPUT)/$(PROJECT)
SOURCE_BINARY_DIR=$(TOPDIR)/bin
SOURCE_BINARY_FILE=$(SOURCE_BINARY_DIR)/$(PROJECT)
SOURCE_MAIN_FILE=main.go
BUILD_TIME=`date +%Y%m%d%H%M%S`
BUILD_FLAG=-ldflags "-X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME)"
OBJTAR=$(OBJ_DIR).tar.gz
all: build pack
@echo "
ALL DONE"
@echo "Program: " $(PROJECT)
@echo "Version: " $(VERSION)
@echo "Env: " $(ENV)
build:
@echo "start go build...."
@rm -rf $(SOURCE_BINARY_DIR)/*
@go build $(BUILD_FLAG) -o $(SOURCE_BINARY_FILE) $(SOURCE_MAIN_FILE)
pack:
@echo "
packing...."
@tar czvf $(OBJTAR) -C $(OBJ_DIR) .Running the Deployment
Typical workflow after implementing the CI pipeline:
git commit -a -m "prepare tag test"
git push
# Create a tag
git tag -a "1.0.0" -m "1.0.0"
git push origin 1.0.0The tag push triggers the GitLab CI pipeline, which builds the binary, packages it, and hands it off to Jenkins (or any deployment tool) for final deployment.
Conclusion
Release processes vary by team size and project complexity. Small teams may still use manual releases, but larger teams benefit from a standardized CI/CD workflow that combines GitLab CI, Makefile automation, and a deployment system to ensure reliable, repeatable releases.
Go Development Architecture Practice
Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
