Jenkins CI with SonarQube Quality Gate on Ubuntu 24.04 LTS on Azure User Guide
Overview
This cloudimg appliance is a DevOps-in-a-box: Jenkins LTS as the CI/CD orchestrator, pre-integrated with SonarQube Community Edition as the static-analysis quality gate. The value over a plain co-install is that the wiring between them is already done — a Jenkins pipeline's withSonarQubeEnv('SonarQube') works on first boot, and waitForQualityGate resolves because SonarQube posts its result back to Jenkins over a pre-configured webhook.
Only the free Community Edition of SonarQube is shipped (LGPL-3.0). The Developer, Enterprise and Data Center editions are separate commercial products and are not included. Jenkins is MIT-licensed.
What is included:
- Jenkins LTS 2.568.2 (MIT) from the official
pkg.jenkins.io/debian-stableAPT repo, on OpenJDK 21, bound to127.0.0.1:8080with the setup wizard disabled - SonarQube Community 10.7 (LGPL-3.0) on OpenJDK 17, bound to
127.0.0.1:9000under the/sonarcontext, backed by a local PostgreSQL and its embedded Elasticsearch (vm.max_map_count=524288persisted) - The integration, pre-wired: the Jenkins SonarQube Scanner plugin; a
SonarQubeserver installation pointing at the local server with a per-VM analysis-token credential; aSonarScannertool at/opt/sonar-scanner; a SonarQube → Jenkins quality-gate webhook; and a seededsonarqube-analysis-demopipeline that runs the whole loop - nginx on
:80as the only public listener —/serves Jenkins,/sonarserves SonarQube (using$http_hostso absolute URLs and the webhook stay correct) - Per-VM secrets, none baked: the Jenkins admin password, SonarQube admin password (the default
admin/adminis rotated and rejected), PostgreSQL role password and SonarQube analysis token are all generated on first boot and written to/etc/cloudimg-credentials.txt(mode0600 root:root) - A dedicated 40 GiB data disk at
/var/lib/sonarqubeholding the SonarQube data/extensions and the PostgreSQL datadir - 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key, a VNet plus subnet, and an NSG allowing inbound TCP 22 (SSH) and TCP 80 (web UI) from your trusted CIDR.
Sizing: this appliance runs two JVMs (Jenkins and SonarQube's web + compute-engine), an embedded Elasticsearch JVM, and PostgreSQL. Measured idle memory of the whole stack is ~4.6 GiB, rising to ~7 GiB while an analysis runs. Standard_B4ms (16 GiB) is the recommended size for a demo or small team; smaller sizes cannot host two JVMs plus Elasticsearch and PostgreSQL. Scale up (D-series) and add distributed Jenkins agents for heavier workloads.
Step 1: Deploy from Azure Marketplace
Search the Azure Marketplace for "cloudimg Jenkins SonarQube" and click Create. Pick the Standard_B4ms size, choose your VNet plus subnet, and ensure the NSG allows inbound TCP 22 and TCP 80 from your trusted CIDR.
Step 2: SSH In
ssh azureuser@<public-ip>
Step 3: Confirm the Stack Is Running
All four services are pre-enabled and started on first boot.
for s in postgresql sonarqube jenkins nginx; do printf '%-14s %s (%s)\n' "$s" "$(systemctl is-active $s)" "$(systemctl is-enabled $s)"; done

Step 4: Read Your Per-VM Credentials
First boot rotates every secret and writes them here (unique to this VM):
sudo cat /etc/cloudimg-credentials.txt
The file (mode 0600 root:root) contains JENKINS_URL / JENKINS_ADMIN_PASSWORD, SONARQUBE_URL / SONARQUBE_ADMIN_PASSWORD, and SONARQUBE_ANALYSIS_TOKEN. Values are unique per VM — the screenshot below shows only the key names, with values redacted:

Step 5: Open Jenkins
Browse to http://<public-ip>/ and sign in with admin and the JENKINS_ADMIN_PASSWORD from Step 4. The dashboard shows the pre-seeded sonarqube-analysis-demo pipeline:

Step 6: Open SonarQube
Browse to http://<public-ip>/sonar and sign in with admin and the SONARQUBE_ADMIN_PASSWORD from Step 4. SonarQube is served under the /sonar path by the same nginx front door.
You can confirm the server is up without signing in:
curl -s http://localhost/sonar/api/server/version; echo
curl -s -o /dev/null -w 'sonar /health: %{http_code}\n' http://localhost/health
Step 7: See the Integration in Action
The seeded sonarqube-analysis-demo pipeline demonstrates the whole loop. Open it in Jenkins — the Stage View shows the SonarQube analysis and Quality gate stages running green:

The analysed project appears in SonarQube with its quality gate result — this is the appliance's core job, working end to end:

You can verify the same thing from the shell. The bundled check runs a real sonar-scanner analysis with the per-VM token and confirms SonarQube processed it and evaluated the gate:
sudo /usr/local/sbin/jenkins-sonarqube-integration-check.sh

Step 8: How the Two Are Wired Together
The SonarQube → Jenkins webhook (used by waitForQualityGate) is pre-configured. In SonarQube under Administration → Configuration → Webhooks you will see the Jenkins webhook with successful deliveries:

On the Jenkins side, a SonarQube server installation and a SonarScanner tool are already configured (Manage Jenkins → System / Tools). That means your own pipelines can use the integration with no setup:
pipeline {
agent any
stages {
stage('SonarQube analysis') {
steps {
script {
def scannerHome = tool 'SonarScanner'
withSonarQubeEnv('SonarQube') {
sh "${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=my-app"
}
}
}
}
stage('Quality gate') {
steps { timeout(time: 5, unit: 'MINUTES') { waitForQualityGate abortPipeline: true } }
}
}
}
Point a job at your own Git repository, add a sonar-project.properties (or pass -Dsonar.sources=...), and the analysis, token and quality-gate feedback all work with no further configuration.
Step 9: Versions
echo "Jenkins: $(curl -sI http://localhost/login | awk '/[Xx]-[Jj]enkins:/{print $2}' | tr -d '\r')"
echo "SonarQube: $(curl -s http://localhost/sonar/api/server/version) (Community Edition)"
java -version 2>&1 | head -1

Security Notes
- No default credentials survive first boot. SonarQube's
admin/adminis rotated to a per-VM password (the default is proven dead), and the Jenkins admin password, PostgreSQL role password and SonarQube analysis token are all generated per VM. - Rotate the admin passwords after first login if you wish, and restrict the NSG to your own management CIDR.
- Put TLS in front for production. nginx terminates plain HTTP on port 80; for internet-facing use, add a TLS certificate (for example with
certbot) and restrict access. Both apps build absolute URLs from their configured base URL, so updatesonar.core.serverBaseURLand the Jenkins URL if you place the appliance behind a different hostname oryour-domain. - The web UIs bind loopback only. Jenkins (
:8080) and SonarQube (:9000) are never exposed directly; nginx on:80is the single public listener.
Support
cloudimg images include 24/7 support. Contact support@cloudimg.co.uk with the VM name and region for help.