CI/CD for Go: Building a Pipeline from Scratch in 20 Minutes

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Your Go Project Begs for CI/CD Like a Dog Begs for a Walk



You've written a cool microservice in Go. It's fast, tests pass, the code is beautiful. But every morning you manually run go build, upload the binary to the server via SSH, and pray that nothing crashes. Sound familiar? If so, I have news for you: you're spending 15% of your life on something a robot can do.



CI/CD (Continuous Integration / Continuous Deployment) is not just a buzzword from DevOps job postings. It's your personal assistant that:

  • runs tests on every push to Git;
  • builds a binary for Linux/amd64, even if you're on a Mac;
  • sends notifications to Telegram if the build fails;
  • automatically deploys to the server when you merge a PR into main.


In this article, we'll set up a CI/CD pipeline for Go from scratch. No fluff, no "install Jenkins on 20 servers." Just GitHub Actions, Docker, and a bit of magic. Let's go!



What We'll Need

For the setup, we'll need:

  • A GitHub repository (you can create a test one right now);
  • A Go project with modules (we'll write a simple HTTP server);
  • A Docker Hub account (or any other registry);
  • 5 minutes of attention and a desire to automate the routine.


All the code we'll use is publicly available. You can copy it and adapt it to your project. Just remember to replace the tokens and repository names with your own.



Step 1: Write a Simple Go Application

For demonstration, let's take a minimal HTTP server. Create a file main.go:



package main

import ( "fmt" "net/http")

func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from Go CI/CD pipeline!") })

http.ListenAndServe(":8080", nil)}


And a file main_test.go to check functionality:



package main

import ( "net/http" "net/http/httptest" "testing")

func TestHelloHandler(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) w := httptest.NewRecorder()

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from Go CI/CD pipeline!") })

http.DefaultServeMux.ServeHTTP(w, req)

if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) }}


Initialize the module:

go mod init github.com/your-username/go-cicd-examplego mod tidy


Check that everything works:

go test ./... -vgo run main.go


If you see ok and a greeting on localhost:8080 — you're awesome. Let's move on.



Step 2: Create a Dockerfile for the Go Application

Containerization is a mandatory step in modern deployment. We'll build a static binary (without CGO) and package it into a minimal image based on scratch or alpine. This will give us an image size of about 10–15 MB instead of 300+ MB.



Create a Dockerfile in the project root:



FROM golang:1.22-alpine AS builder

WORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -o main .

FROM alpine:latestRUN apk --no-cache add ca-certificatesWORKDIR /root/COPY --from=builder /app/main .EXPOSE 8080CMD ["./main"]


Note: we use multi-stage builds. The first stage is compilation in a full Go environment. The second is a minimal Alpine image, where only the binary is copied. This is the industry standard for Go applications.



Build the image locally and check:

docker build -t go-cicd-example .docker run -p 8080:8080 go-cicd-example


If everything is ok — let's move on to

Blogs

Book Recommendations