DEV Community

Cover image for Building, Securing, and Deploying a Go App with GitLab CI/CD EP 3: SonarQube
Booranasak Kanthong
Booranasak Kanthong

Posted on • Edited on

Building, Securing, and Deploying a Go App with GitLab CI/CD EP 3: SonarQube

Welcome back to EP 3 of my CI/CD Security Series!

In this episode, we’ll focus entirely on integrating SonarQube into your GitLab CI/CD pipeline. SonarQube is a powerful tool for automatically scanning your code for bugs, vulnerabilities, and code smells—right inside your pipeline.

If you missed EP 1 and 2, make sure to check it out for a full overview of the complete CI/CD security flow. Now, let’s get our hands dirty and bring SonarQube into the pipeline!


Getting Started with SonarQube

After you log in to SonarQube, you’ll land on this dashboard:

Start by clicking Create Project → Local Project.

Now, name your project as you like.

It’s a good idea to check your global settings here, too:


Integrating SonarQube with GitLab CI/CD

For this project, we’ll be using GitLab to build our CI/CD pipeline.

Click With GitLab CI to get started.

You’ll see a setup page like this:

You can follow these steps directly, but I’ll break them down for you for clarity.


Step 1: Add Environment Variables in GitLab

First, let’s add the necessary environment variables.

Head to your project in GitLab. On the sidebar, go to Settings (hover if you don’t see the label):

Then click CI/CD → Variables.

Now, click Add variable.

A sidebar will pop up:

  • For Key, use the value from SonarQube (e.g., SONAR_TOKEN).
  • For Value, go back to SonarQube and click Generate Token:

Choose your expiration date—although I usually set it to “No Expiration” for testing, I recommend setting an expiration for security reasons.

Copy your token and paste it into GitLab:


If you are working on branches other than main (for example, develop, feature, etc.), you also need to configure those branches as Protected branches.

Go to Settings → Repository:

Add a Protected Branch:

Type in your branch name:

Click Protect

Now, you’ll be able to use protected variables on these branches!


Step 2: Add SonarQube Config to Your Pipeline

Next, grab your project key from SonarQube and add it as a GitLab variable:

It should look like this:

Pick your programming language. I’m using Go for this demo:

If you don’t have a sonar-project.properties file, create one:

sonar.projectKey=booranasak-bank-golang-sast
sonar.qualitygate.wait=true
Enter fullscreen mode Exit fullscreen mode

Step 3: Update Your .gitlab-ci.yml

Now, update your .gitlab-ci.yml with the following content.
(Make sure your rules include the branches you want SonarQube to run on!)

image: 
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]

variables:
  SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
  GIT_DEPTH: "0"

stages:
  - sonarqube-check
  - sonarqube-vulnerability-report

sonarqube-check:
  stage: sonarqube-check
  script: 
    - sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}"
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'

sonarqube-vulnerability-report:
  stage: sonarqube-vulnerability-report
  script:
    - 'curl -u "${SONAR_TOKEN}:" "${SONAR_HOST_URL}/api/issues/gitlab_sast_export?projectKey=booranasak-bank-golang-sast&branch=${CI_COMMIT_BRANCH}&pullRequest=${CI_MERGE_REQUEST_IID}" -o gl-sast-sonar-report.json'
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'
  artifacts:
    expire_in: 1 day
    reports:
      sast: gl-sast-sonar-report.json
Enter fullscreen mode Exit fullscreen mode

Note:
By default, this pipeline only runs SonarQube on these branches:

  • master
  • main
  • develop Or on merge requests.

If you want to run SonarQube on another branch (like feature/sonarqube for testing), simply update the rules:

rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'
    - if: $CI_COMMIT_BRANCH == 'feature/sonarqube'
Enter fullscreen mode Exit fullscreen mode

That’s it! Just adjust your rules for any branches you want to include.


And now, your .gitlab-ci.yml should look something like this (I merge SonarQube into one stage but the job still separate for clarity and easier management):

sonarqube-check:
  stage: sast
  image:
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]
  dependencies:
    - unit_test_and_coverage
  script: 
    - sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}" -Dsonar.go.coverage.reportPaths=coverage.out -Dsonar.exclusions=**/*_test.go
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'

sonarqube-vulnerability-report:
  stage: sast
  image:
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]
  script:
    - 'curl -u "${SONAR_TOKEN}:" "${SONAR_HOST_URL}/api/issues/gitlab_sast_export?projectKey=booranasak-golang-sast&branch=${CI_COMMIT_BRANCH}&pullRequest=${CI_MERGE_REQUEST_IID}" -o gl-sast-sonar-report.json'
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'
  artifacts:
    expire_in: 1 day
    reports:
      sast: gl-sast-sonar-report.json
Enter fullscreen mode Exit fullscreen mode

And when you merge everything together, It will look like this

image: docker:latest

services:
  - docker:dind

variables:
  DOCKER_HOST: tcp://docker:2375/
  DOCKER_TLS_CERTDIR: ""
  GO_VERSION: "1.24.3"
  SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
  GIT_DEPTH: "0"

stages:
  - lint
  - test
  - sast

.go-job-template: &go-job-template
  image: debian:bullseye
  before_script:
    - apt update && apt install -y curl git tar gzip
    - curl -LO https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
    - rm -rf /usr/local/go && tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
    - export PATH="/usr/local/go/bin:$PATH"
    - go version

lint_golint:
  stage: lint
  <<: *go-job-template
  script:
    - export PATH="/usr/local/go/bin:$PATH"
    - go install golang.org/x/lint/golint@latest
    - export PATH="$PATH:$(go env GOPATH)/bin"
    - echo "Linting files:"
    - find . -name '*.go'
    - golint ./... | tee lint-report.txt
    - echo "--- Lint report preview ---"
    - cat lint-report.txt || echo "lint-report.txt is empty"
  allow_failure: true
  artifacts:
    name: "golint-report"
    paths:
      - lint-report.txt
    expire_in: 1 week

unit_test_and_coverage:
  stage: test
  <<: *go-job-template
  script:
    - export PATH="/usr/local/go/bin:$PATH"
    - go mod tidy
    - go test -v -cover ./...
    - go test -v -coverprofile=coverage.out ./...
  artifacts:
    paths:
      - coverage.out
    expire_in: 1 hour

sonarqube-check:
  stage: sast
  image:
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]
  dependencies:
    - unit_test_and_coverage
  script: 
    - sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}" -Dsonar.go.coverage.reportPaths=coverage.out -Dsonar.exclusions=**/*_test.go
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'

sonarqube-vulnerability-report:
  stage: sast
  image:
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]
  script:
    - 'curl -u "${SONAR_TOKEN}:" "${SONAR_HOST_URL}/api/issues/gitlab_sast_export?projectKey=booranasak-golang-sast&branch=${CI_COMMIT_BRANCH}&pullRequest=${CI_MERGE_REQUEST_IID}" -o gl-sast-sonar-report.json'
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'
  artifacts:
    expire_in: 1 day
    reports:
      sast: gl-sast-sonar-report.json

Enter fullscreen mode Exit fullscreen mode

Pipeline Example

Sonarqube Example


Failed Example

  • First issue are code coverage below 80 percent

  • Second issue are Duplicated string literals

You can also view more detail

even explain why it was an issue

Even more!!!, Explain how to fix it


Simple, right? With these steps, you can integrate SonarQube into your GitLab CI/CD pipeline and customize it to fit your workflow.


Top comments (0)