Repos for this article's reference:
Continuous Integration (CI) is a key practice in modern software development that helps teams detect bugs early, maintain code quality, and automate testing.
With GitHub Actions, Go developers can easily build automated CI pipelines directly from their GitHub repositories — without needing external tools.
In this guide, you’ll learn how to:
- Set up GitHub Actions for Go applications.
- Automate build, test, and lint processes.
- Add caching for faster CI runs.
- Maintain consistent code quality using linters and tests.
By the end, you’ll have a working CI workflow that runs automatically every time you push code or open a pull request.
What Is Continuous Integration (CI)?
Continuous Integration (CI) is the practice of automatically building and testing code changes whenever developers commit them.
This ensures that:
- Errors are caught early.
- Code always stays in a deployable state.
- Merging branches doesn’t break existing functionality.
GitHub Actions provides a cloud-based CI/CD system built right into GitHub. It allows you to create YAML workflows that automatically run when specific events occur — like a push or pull request.
Setting Up a Go Project
Let’s assume you already have a simple Go project. If not, create one:
mkdir go-ci-demo
cd go-ci-demo
go mod init github.com/username/go-ci-demo
go mod tidy
Example project structure:
go-ci-demo/
├── main.go
├── internal/
│ └── service.go
└── tests/
└── service_test.go
main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, CI Pipeline!")
}
internal/service.go
package internal
func Add(a, b int) int {
return a + b
}
tests/service_test.go
package tests
import (
"testing"
"github.com/username/go-ci-demo/internal"
)
func TestAdd(t *testing.T) {
result := internal.Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, got %d", result)
}
}
Creating the GitHub Actions Workflow
GitHub Actions workflows live in .github/workflows/.
Create a new file named .github/workflows/ci.yml.
name: Go CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Install dependencies
run: go mod tidy
- name: Build
run: go build -v ./...
Explanation:
ondefines triggers — this runs on pushes and pull requests tomain.jobsdefine a series of steps executed on a runner (ubuntu-latest).- The steps check out the repo, set up Go, install dependencies, and build the project.
Adding Automated Testing
Now, let’s add a test step to ensure code correctness.
- name: Run tests
run: go test -v ./...
This step will:
- Run all unit tests in your Go project.
- Display verbose logs in GitHub Actions.
- Fail the CI pipeline if any test fails.
You can also add coverage reports:
- name: Run tests with coverage
run: go test -coverprofile=coverage.out ./...
This generates a file coverage.out that shows what percentage of your code is covered by tests.
Adding Linting for Code Quality
Linting ensures code follows best practices and detects common mistakes.
We’ll use golangci-lint, a popular Go linter that aggregates multiple linters into one tool.
Add these steps to your workflow:
- name: Install golangci-lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s latest
./bin/golangci-lint run ./...
This step:
- Installs the latest version of
golangci-lint. - Runs it against your Go codebase.
- Fails the pipeline if any lint errors are found.
Full CI Workflow Example
Here’s the full combined GitHub Actions workflow.
.github/workflows/ci.yml
name: Go CI Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-test-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install dependencies
run: go mod tidy
- name: Build
run: go build -v ./...
- name: Run tests
run: go test -v ./...
- name: Run golangci-lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s latest
./bin/golangci-lint run ./...
Testing the Workflow
To test your workflow:
-
Commit and push your code to GitHub:
git add . git commit -m "Add GitHub Actions CI pipeline" git push origin main -
Go to the Actions tab in your GitHub repository.
-
You’ll see the “Go CI Pipeline” workflow running.
-
Check logs for each step (Build, Test, Lint).
If everything passes, the workflow will show a green checkmark ✅ next to your commit.
Adding Caching for Faster Builds
Caching helps reduce build times by reusing previously downloaded dependencies. We already added caching in the example, but let’s break it down.
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
This step:
- Saves your Go build cache and modules.
- Restores them in subsequent runs.
- Greatly speeds up repeated workflows.
Optional Enhancements
You can extend your CI workflow to include:
- Static analysis with
go vet ./... - Integration tests for API or database layers.
- Build Docker images and push to a container registry.
- Deploy to production environments after successful builds.
- Slack/Discord notifications for failed builds.
Example snippet for static analysis:
- name: Run static analysis
run: go vet ./...
Best Practices
- Run lint and test stages before merging to main.
- Keep workflows simple and modular.
- Use version-pinned actions (
@v4,@v5) for stability. - Protect your
mainbranch with required CI checks. - Avoid unnecessary dependencies to speed up builds.
Conclusion
You’ve now created a full Continuous Integration pipeline for your Go application using GitHub Actions.
This workflow automatically:
- Builds your Go project.
- Runs tests and linting.
- Caches dependencies for faster performance.
With CI in place, every commit and pull request is verified automatically — ensuring reliable, high-quality software with minimal manual effort.