SonarQube analyses code for bugs, vulnerabilities, code smells, duplication and test coverage. This post runs it the way a team would: a persistent PostgreSQL database, a reverse proxy with TLS, correct host settings, and a plan for backups and upgrades.
Versions in use on this date. SonarQube Server 10.4 was published on 6 February 2024. The previous long-term active version (LTA) is 9.9, so a 9.x instance must go through 9.9 before it can reach 10.x. The free edition is the Community Build: it analyses the main branch only (no branch or pull request analysis) and supports fewer languages than paid editions.
Architecture
flowchart TB
DEV[Developers browser] -->|HTTPS 443| NG[NGINX reverse proxy<br/>TLS certificate]
CI[CI jobs with sonar-scanner] -->|HTTPS and token| NG
NG -->|9000| SQ[SonarQube container<br/>web server compute engine search]
SQ -->|JDBC| PG[(PostgreSQL 15<br/>volume sonar_db)]
SQ --- V1[Volumes<br/>data extensions logs]
PG -->|nightly pg_dump| BK[Backup storage]
SonarQube embeds Elasticsearch for search. That is why the host needs specific kernel settings, and why it must not run as root.
Host preparation
Elasticsearch refuses to start unless these are set on the Docker host:
sudo tee /etc/sysctl.d/99-sonarqube.conf <<'EOT'
vm.max_map_count=524288
fs.file-max=131072
EOT
sudo sysctl --system
The container needs nofile of at least 131072 and nproc of 8192, which the Compose file sets below. Give the VM at least 4 GB RAM (the three internal Java processes are sized from it) and a data disk that can grow.
docker-compose.yml
services:
db:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD_FILE: /run/secrets/sonar_db_password
POSTGRES_DB: sonar
volumes: [sonar_db:/var/lib/postgresql/data]
secrets: [sonar_db_password]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sonar"]
interval: 10s
retries: 5
sonarqube:
image: sonarqube:10.4.0-community
restart: unless-stopped
depends_on:
db: { condition: service_healthy }
environment:
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD_FILE: /run/secrets/sonar_db_password
ports: ["127.0.0.1:9000:9000"]
ulimits:
nofile: { soft: 131072, hard: 131072 }
nproc: 8192
volumes:
- sonar_data:/opt/sonarqube/data
- sonar_extensions:/opt/sonarqube/extensions
- sonar_logs:/opt/sonarqube/logs
secrets:
sonar_db_password:
file: ./secrets/sonar_db_password.txt
volumes: { sonar_db: {}, sonar_data: {}, sonar_extensions: {}, sonar_logs: {} }
Pin an exact image tag (not latest), publish port 9000 on localhost only, and let the proxy handle the outside. Note the official image reads _FILE variants only for some settings; if your version does not, pass the password through an environment file with restricted permissions instead.
mkdir -p secrets && openssl rand -base64 24 > secrets/sonar_db_password.txt
docker compose up -d
docker compose logs -f sonarqube # wait for "SonarQube is operational"
Sign in at the proxy URL with admin / admin, and change the password immediately.
NGINX in front
server {
listen 443 ssl;
server_name sonar.example.com;
ssl_certificate /etc/ssl/sonar.crt;
ssl_certificate_key /etc/ssl/sonar.key;
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Backups
All analysis history lives in PostgreSQL, so that is what matters:
docker compose exec -T db pg_dump -U sonar -Fc sonar > /backup/sonar-$(date +%F).dump
Keep the extensions volume too if you install plugins. Practise restoring into a fresh stack.
Upgrading
- Read the upgrade notes for the target version, and check plugin compatibility.
- Back up the database.
- Change the image tag, run
docker compose pull && docker compose up -d. - Open
/setupas an admin to run the database migration, then check the logs.
Failure modes
- Container exits with a bootstrap check error:
vm.max_map_countor file limits are too low on the host. - Startup hangs on the database: wrong JDBC URL, or PostgreSQL not ready. Use the health check above.
- Out of memory: raise VM memory before tuning Java options.