Developer Tools Azure

Jenkins CI with SonarQube Quality Gate on Ubuntu 24.04 LTS on Azure User Guide

| Product: Jenkins CI with SonarQube Quality Gate on Ubuntu 24.04 LTS on Azure

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-stable APT repo, on OpenJDK 21, bound to 127.0.0.1:8080 with the setup wizard disabled
  • SonarQube Community 10.7 (LGPL-3.0) on OpenJDK 17, bound to 127.0.0.1:9000 under the /sonar context, backed by a local PostgreSQL and its embedded Elasticsearch (vm.max_map_count=524288 persisted)
  • The integration, pre-wired: the Jenkins SonarQube Scanner plugin; a SonarQube server installation pointing at the local server with a per-VM analysis-token credential; a SonarScanner tool at /opt/sonar-scanner; a SonarQube → Jenkins quality-gate webhook; and a seeded sonarqube-analysis-demo pipeline that runs the whole loop
  • nginx on :80 as the only public listener — / serves Jenkins, /sonar serves SonarQube (using $http_host so absolute URLs and the webhook stay correct)
  • Per-VM secrets, none baked: the Jenkins admin password, SonarQube admin password (the default admin/admin is rotated and rejected), PostgreSQL role password and SonarQube analysis token are all generated on first boot and written to /etc/cloudimg-credentials.txt (mode 0600 root:root)
  • A dedicated 40 GiB data disk at /var/lib/sonarqube holding 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

postgresql, sonarqube, jenkins and nginx all active and enabled

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:

ls -l showing /etc/cloudimg-credentials.txt at mode 0600 root:root, and the credential key names with values masked

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:

Jenkins dashboard showing the seeded sonarqube-analysis-demo pipeline job with a successful last build

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:

Jenkins Stage View of the demo pipeline with SonarQube analysis and Quality gate stages succeeding

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

SonarQube project overview for cloudimg-integration-check showing Quality Gate Passed

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

Terminal output of the integration check: analysis submitted and visible in SonarQube, quality gate OK, default admin dead

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:

SonarQube Webhooks admin page showing the Jenkins webhook to the local Jenkins with a successful last delivery

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

Jenkins, SonarQube Community, Java and SonarScanner version output

Security Notes

  • No default credentials survive first boot. SonarQube's admin/admin is 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 update sonar.core.serverBaseURL and the Jenkins URL if you place the appliance behind a different hostname or your-domain.
  • The web UIs bind loopback only. Jenkins (:8080) and SonarQube (:9000) are never exposed directly; nginx on :80 is the single public listener.

Support

cloudimg images include 24/7 support. Contact support@cloudimg.co.uk with the VM name and region for help.