Introduction
When it comes to Code Review, it is very important to identify errors or bugs as early as possible in the software life cycle. It helps you detect security issues in the first place without shipping code to production, and later you realise that someone has hacked your system due to your code bug, or simply because you accidentally hard-coded a secret key or password in plain text, and submitted them to the git repository, etc.
Many tools can help you automate code analysis, but in this blog, I want to share information about SonarQube, an on-premises analysis tool designed to detect coding issues. It’s used to integrate with one of the CI pipeline tools (Jenkins, GitLab CI, etc.) to streamline the code analysis of a project against an extensively defined set of rules that includes many attributes of code, such as maintainability, reliability, and security issues on each merge/pull request. I would like to show how we leverage the power of GitLab CI to help us achieve this.

Prerequisite
- Prepare a Linux/Mac/Windows server with Docker installed. Should have a public IP so that GitLab CI can send results to the SonarQube server.
- Basic understanding of how SonarQube works.
- Basic GitLab CI/CD setup.
Install Docker
You can install any Linux-based OS on either an AWS EC2, your Home Lab VirtualBox, or any other similar platform (DigitalOcean, etc.). In my demo, I installed Docker on my Windows OS and used the Windows Subsystem for Linux to interact with Docker.
- Ubuntu – https://docs.docker.com/engine/install/ubuntu/
- Macbook – https://docs.docker.com/desktop/setup/install/mac-install/
- Windows – https://docs.docker.com/desktop/setup/install/windows-install/
Set up SonarQube
To set up a SonarQube server, we need these components:
- A SonarQube docker image
- A database for storing SonarQube results, we use a Postgres server built from a Docker image.
I will create these components using a Docker Compose file to speed up the setup. You can check this setup on my project https://gitlab.com/binhdt2611/sonarqube-docker-compose. Otherwise, it will take you longer to fully set up separate components without using Docker. It’s up to you to choose your setup method, but the configuration steps for SonarQube are the same. If you want to see the installation requirements for other methods, visit their documentation here to learn more.
1. Configure environment settings
If you use a Linux-based server where you installed Docker. You must set the following:
sudo sysctl -w vm.max_map_count=524288
sudo sysctl -w fs.file-max=131072
sudo ulimit -n 131072
sudo ulimit -u 8192
That’s because SonarQube Server uses an embedded Elasticsearch, we need to make sure our host configuration complies with the Elasticsearch production mode requirements and File Descriptors configuration
2. Set up Docker Compose file
(Optional) Next, on your Linux server, create a project folder /data/sonarqube, and go to it. If you are in the folder where you want to create the compose.yaml file already, just skip this step.
sudo mkdir -p /data/sonarqube && cd -
Run sudo nano compose.yaml to create a file with content:
services:
sonarqube:
build:
# we need to create sonarqube folder where it stores Dockerfile inside.
context: ./sonarqube
container_name: sonarqube
restart: always
ports:
- "9000:9000"
environment:
- SONAR_JDBC_URL=${SONAR_JDBC_URL}
- SONAR_JDBC_USERNAME=${SONAR_JDBC_USERNAME}
- SONAR_JDBC_PASSWORD=${SONAR_JDBC_PASSWORD}
volumes:
- sonarqube-data:/opt/sonarqube/data
- sonarqube-extensions:/opt/sonarqube/extensions
- sonarqube-logs:/opt/sonarqube/logs
depends_on:
sonar-db:
condition: service_started
required: true
networks:
- sonar
sonar-db:
image: postgres:15
container_name: sonar-db
restart: unless-stopped
ports:
- "5432:5432"
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
networks:
- sonar
volumes:
- database-data:/var/lib/postgresql/data
volumes:
sonarqube-data:
sonarqube-logs:
sonarqube-extensions:
database-data:
networks:
sonar:
name: sonar-network
driver: bridgePress Ctrl + O and press Enter to save the content. Then press Ctrl + X to exit.
Create a sonarqube/ folder to store the Dockerfile where docker compose used to build the image.
sudo mkdir sonarqube && touch Dockerfile
Run sudo nano sonarqube/Dockerfile and add the following content:
FROM sonarqube:25.5.0.107428-communityPress Ctrl + O and press Enter to save the content. Then press Ctrl + X to exit.
The reason we create a Dockerfile instead of adding directly on compose.yml file because we might later want to add more custom configurations to the SonarQube image. So better to create this file for the future use.
Add .env file for Docker Compose file. Run sudo nano .env and add the content:
# SonarQube Settings
export SONAR_JDBC_URL=jdbc:postgresql://sonar-db:5432/sonarqube
export SONAR_JDBC_USERNAME=sonar
export SONAR_JDBC_PASSWORD=sonar
# Postgres Settings
export POSTGRES_USER=sonar
export POSTGRES_PASSWORD=sonar
export POSTGRES_DB=sonarqubePress Ctrl + O and press Enter to save the content. Then press Ctrl + X to exit.
Run these commands to set up:
source .env
docker compose up -d
Once it’s done, you can check Docker Desktop under Containers section. There are two running containers: sonarqube and sonar-db are created.

Or running command line in Linux: docker ps. We got result.

Port Access
- Set up self-hosted SonarQube on AWS EC2: you need to open Security Group port
9000/tcpto allow GitLab CI/CD host to send analysis result to the SonarQube server through<your-ip-public-of-sonarqube-server>:9000. Similar to other cloud providers.- Set up self-hosted SonarQube on local network: If your ISP allows to configure NAT Port Forwarding on the modem, you can configure to allow access from the internet on
your-ip-public:9000toyour-local-ip-sonarqube-server:9000. If your ISP doesn’t allow to configure Port Forwarding, you can follow me to usengrok
(Optional) Configure and install ngrok
You can skip this step, and use direct IP of your SonarQube server if you can access from the internet. Because I’m running SonarQube on docker at local network and my ISP block Port Forwarding. I’m going to install ngrok, simply an API gateway which help us access the SonarQube in a private network from the internet without exposing the server to the internet.
You can sign up an ngrok account and follow this docs to install it. Once you have ngrok installed, open terminal and simply run:
ngrok http 9000
(Optional) ngrok allows us to create a free static domain if you like, after you created ngrok account, can follow the doc to create a domain. Once it’s done, can run this command to bind the static domain to our SonarQube server (change --url= below to <your own domain>):
ngrok http --url=integral-honestly-bulldog.ngrok-free.app 9000
Output:

Ngrok will create a link that allows you to access from the internet, and forward all requests to your SonarQube server host at http://localhost:9000. If you don’t use a static domain, every time you run ngrok http 9000, it will generate a different domain for you, so be aware of that.
Now, we will log in to the link created by ngrok. My link is https://integral-honestly-bulldog.ngrok-free.app. Your link should be different from mine. Click Visit Site.

It’s going to take you to SonarQube login page. Log in with SonarQube default user/password is admin/admin.

It’s going to ask you to change the password. Change to the desired password you want, and click Update.

Then, it will take you to SonarQube dashboard, can select Later to skip the introduction if you want, see as below:

Before we import our projects on GitLab into SonarQube to analyse. We need to set up a Personal Access Token on GitLab for SonarQube to use.
Preparing GitLab Integration
1. Set up Server URL on SonarQube server.
You must configure your SonarQube Server base URL to make the integration work properly.
- Go to Administration > Configuration > General Settings > General > General.
- In Server base URL, set to the
http://your-sonarqube-server-public-ip:9000.
In my case, it’s going to be ngrok link: https://integral-honestly-bulldog.ngrok-free.app as it forwards to http://localhost:9000. If you use your own domain pointing to your public IP, where SonarQube server behinds a reverse proxy like ngrok. It should be your domain, for example: https://your-example-domain.com.

2. Configure personal access token on GitLab
Create a Personal Access Token on GitLab with at least api permission for SonarQube , go to https://gitlab.com/-/user_settings/personal_access_tokens, and follow these steps:
- Select your avatar and select Edit profile.
- Select Access tokens.
- Select Add new token
- and fill in the content as below
- Select Create personal access token.

Copy the newly created access token. And go back to the SonarQube dashboard.

Import the project on GitLab into SonarQube
Once we have a Personal Access Token, go back to the SonarQube dashboard, click Setup at Import from GitLab.

A Configuration Box appears and ask you to fill in the fields:
- Configuration name: I named to “GitLab Integration”. <you-can-choose-anything>
- GitLab API URL: I’m using GitLab.com, so it should be
https://gitlab.com/api/v4. If you install a self-hosted GitLab, should enter your own domain here. E.g.https://your-own-domain.com/api/v4 - Personal Access Token: Paste your GitLab created token here.

Click Save configuration to continue.
Then, it’s going to ask you personal access token again to grant access to read all your projects on GitLab. Fill in your token and click Save.

At the GitLab project onboarding dashboard, select your project to import into SonarQube and click Import. My project is “my-grpc-go-server”, a GoLang project.

It’s going to ask you set up Clean as You Code methodology, just select Use the global setting, and click Create project.

Once the project creation process completes, it will take you to your main project, then select With GitLab CI to follow its tutorial.

See the next section to integrate SonarQube into GitLab CI/CD.
Integrate SonarQube into GitLab CI/CD
1. Add environment variables
Now, we need to follow the instructions to configure GitLab CI/CD environment variables. They’re used for SonarScanner, a program that runs on the CI/CD host to perform the source code analysis and send the analysis result back to the SonarQube Server. The SonarQube solution offers standard types of scanners supported mainly for Gradle, Maven, .NET, NPM, and Python. Because I’m analysing a Golang project, I will use SonarScanner CLI instead of GitLab CI/CD, which requires several manual configurations.
Now, we need to copy these key-value variables below to a temporary note:
- The Build Token environment variable:
- key: SONAR_TOKEN
- value: see the Generate a token step
- The Build URL environment variable(in SonarQube version 25.5.0.107428-community somehow doesn’t show but we should have something like this in other versions):
- key: SONAR_HOST_URL
- value: again, my one is https://integral-honestly-bulldog.ngrok-free.app. You should do your own. E.g. replace
http://localhost:9000tohttp://your-public-ip:9000

Click Generate a token -> enter token name -> select desired expiry -> click Generate.

Once the SonarQube token is generated, copy it and save it to a temporary place to configure later.

Then, you will go to your project on GitLab. I’ll do the same with my my-grpc-go-server to configure CI/CD variables.

Select Add variable and fill in the details, e.g.
- Key: SONAR_TOKEN
- Value: the generated token

Doing the same for the other:
- key: SONAR_HOST_URL
- value:
https://integral-honestly-bulldog.ngrok-free.app

2. Set up Sonar project key
At this step, it depends on what project you are trying to analyse. I’m analysing the Golang project, so I will choose this Other option. If you analyse a Java, .Net, or others, just select the corresponding option. Do not click Continue yet, just copy the generated codes as below, and continue the next step.

Next, we need to create a sonar-project.properties file under our own project’s root directory that we’re analysing, and paste the content we copied above into the file. And add few basic options as below:
# Unique project key registered in SonarQube server
sonar.projectKey=binhdt2611_my-grpc-go-server_2a36482e-4893-41e9-a3a9-2b08b1a8dbb1
# Ensures GitLab pipeline waits for quality gate results
sonar.qualitygate.wait=true
# Human-readable project name in SonarQube. Default is projectKey
sonar.projectName=my-grpc-go-server
# Defines the codebase directory where sonar-scanner should analyze.
# Set to "." to start from project's root directory
sonar.sources="."
# Excludes unnecessary files/folders from analysis. Enable this if you want to exclude.
# E.g. vendors/**/* in PHP or node_modules/** in NodeJS Project.
# sonar.exclusions=module-to-exclude/**/*It’s showing as follows in my project:

3. Set up .gitlab-ci.yml configuration
Next, go back to the SonarQube dashboard, can copy the content template of.gitlab-ci.yml showing there OR copy the content I put below.

Go to your project, and we will create the .gitlab-ci.yml file under the project’s root directory, and add the following content:
image:
name: sonarsource/sonar-scanner-cli:11
entrypoint: [""]
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
# Define a sequence of stages
stages:
- build-sonar
build-sonar:
stage: build-sonar
# To prevent SonarScanner from re-downloading language analyzers each time you run a scan
cache:
policy: pull-push
key: "sonar-cache-$CI_COMMIT_REF_SLUG"
paths:
- "${SONAR_USER_HOME}/cache"
- sonar-scanner/
script:
# Run sonar-scanner program built-in in the docker image.
- sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}"
allow_failure: true # allows job to fail
rules:
# Pipeline should be triggered in the order rules of precedence (from top to bottom)
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_COMMIT_BRANCH == 'master'
- if: $CI_COMMIT_BRANCH == 'main'
Finally, in your project, you need to commit and push two files: .gitlab-ci.yml and sonar-project.properties to the GitLab repository to start the analysis. Run:
git add . && git commit -m "#Set up SonarScanner CI Job"
git push origin main
I’ve got a pipeline that runs successfully.

Now go back to the SonarQube dashboard. We will see the scan result there

Congratulations! You have integrated SonarQube into GitLab CI/CD successfully. For now, every time you push code to the main branch, your code will be analysed, and SonarScanner CLI will send the result to the SonarQube server.
(Optional) Bonus: Implement Pull Request Analysis for the code base on GitLab CI/CD.
The above demo is just a basic integration, and you might find it not useful as it continues analyzing the main branch where the old code is scanned along with new code every time you push your code to the main branch. That’s where the Pull Request Analysis or Branch Analysis comes into play, which allows you to analyse the code on a Merge Request (GitLab) or a feature branch instead. New issues with old code are not reported, as only new code issues are reported.
And if you have a large project, you could eventually create some tools that filter the code changes on every Merge Request, allowing you to analyse the code changes you submitted only, rather than re-analyse the entire code in that branch. It might be against what SonarQube should be used for, but just in case you understand what you’re doing.
Although SonarQube community versions don’t have these Pull Request Analysis or Branch Analysis features, we have to switch to at least the Developer Edition to start using them. However, there’s a plugin called https://github.com/mc1arke/sonarqube-community-branch-plugin that helps us achieve this. This plugin is developed by the community, not by SonarQube and is likely to diverge in the future, so you should consider this before starting to use it.
NOTED:
- At the time of this writing, it supports the newest SonarQube version 24.12.0.100206-community. The SonarQube image that we are using for the demo is 25.5.0.107428-community (not supported yet). So if we want to use this plugin, we have to downgrade to a supported version.
- The sonarqube-community-branch-plugin may release newer versions in the future, so better to checkout their release on github.
1. Clean up before reinstalling
If you are building it anew, to make it simplest, we should delete the previous SonarQube server and rebuild it by following the steps from the beginning. To destroy the current setup, we simply run:
docker compose down
docker volume rm vol-1 vol-2 vol-3
Where vol-* are persistent data stores named (you have to check your own volumes’ name) which created by Docker compose. You can check by running docker volume ls to view its names.
2. Changing SonarQube image
In the adding SonarQube image step, instead of using sonarqube:25.5.0.107428-community, we use:
FROM mc1arke/sonarqube-with-community-branch-plugin:24.12.0.100206-communityAnd continue following the rest of the guide until you have completed pushing your code to the main branch to start the initial analysis.
Once it completes, on your local branch, create a branch named feature-a for testing purposes. You might want to change rules to specify when to run the GitLab CI job. In the file .gitlab-ci.yml, change the content at the build-sonar job if you copy my configuration template, or it should be sonarqube-check on version 24.12.0.100206-community.
build-sonar:
stage: build-sonar
...
...
rules:
# Make the pipeline run only when creating a Merge Request
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'If you don’t want to change it, simply keep the current configuration. Commit and push your changes to the remote repository, then go to GitLab to create a Merge Request to merge the feature-a branch into the main branch.
Once you create the Merge Request, there’s a pipeline that starts running.

Checking the build-sonar output:

Once the build-sonar job finishes, you will see a decoration of the analysis result posted on your Merge Request, and a new SonarQube job will appear then. You can click the SonarQube job, which redirects you to the SonarQube merge request link on the SonarQube server.

Go back to the SonarQube dashboard on the feature-a branch of the project to view the analysis results from the Merge Request.

You might notice that I used to have a security issue when I scanned the main branch (please look at the photo that shows the main branch analysis). But it didn’t show up here again on the Merge Request? As I just said above, issues with old code are not reported, as only new code issues are reported.
Ok. You have implemented a Pull Request Analysis feature to evaluate your codebase upon creating a Merge Request, allowing you to catch bugs early and prevent major issues before shipping code to production.
I hope this blog helps you streamline the code review workflow while securing your code in an automated manner.
If you like my blog post and want to find a way to thank me, feel free to Buy me a coffee to support me in providing more quality content, or simply enjoy reading it and sharing it with others who may have the same issue. Thanks for reading my blog.
Discover more from Turn DevOps Easier
Subscribe to get the latest posts sent to your email.

