ADR-0020: SonarQube Self-Hosted Quality Gate for Cornerstone CI/CD
Status: accepted Deciders: DeAcero Platform Team Date: 2026-03-26
Context and Problem Statement
Cornerstone generates projects with ADR-gate, linting (ruff), type checking (mypy), and architectural layer validation (import-linter). However, there is no centralized static analysis quality gate that tracks code smells, coverage trends, duplications, security hotspots, and vulnerability history across all generated projects over time.
As Cornerstone scales toward the 11 MCP agents target (F3), a shared quality gate running on 192.99.38.188 (vpc.kronosb.com) — which is a temporary measure until dedicated hardware availability is secured — enables:
- Visibility into code health per project over time
- Security hotspot detection aligned with ADR-0007 (OWASP CI/CD posture)
- A single dashboard for the Platform Team to monitor code quality across all Cornerstone-generated projects
Decision Drivers
- Must be self-hosted — no third-party SaaS (consistent with ADR-0001 telemetry philosophy)
- Must integrate into existing GitHub Actions CI workflows without breaking the ADR-gate or test jobs
- Must run on the existing VPS (
192.99.38.188) alongside the Observability Service (Docker Compose already active) - Zero-impact on generated projects that do not configure
SONAR_TOKEN— the scan step is skipped if the secret is absent - Must not require changes to generated project source code — scanner is configured via
sonar-project.properties
Considered Options
- SonarQube Community Edition — self-hosted on
192.99.38.188 - SonarCloud — SaaS by SonarSource
- CodeClimate — SaaS alternative
- Semgrep OSS — CLI-only, no persistent dashboard
Decision Outcome
Chosen option: Option 1 — SonarQube Community Edition, self-hosted via Docker Compose on 192.99.38.188.
Rationale:
- SaaS options (2, 3) raise data residency concerns for DeAcero source code (consistent with ADR-0001)
- Semgrep OSS (4) has no persistent dashboard or trend history — cannot track quality evolution over time
- SonarQube Community Edition is free, battle-tested, and integrates natively with GitHub Actions via sonarsource/sonarqube-scan-action
- The existing VPS already runs Docker Compose with nginx-proxy — SonarQube fits naturally into that stack
Architecture
vpc.kronosb.com (192.99.38.188)
├── services/observability/ → port 8000 (FastAPI + PostgreSQL:5432)
└── services/sonarqube/ → port 9000 (SonarQube + PostgreSQL:5433)
GitHub Actions CI (generated projects)
└── sonar job
├── runs after: test job
├── uses: sonarsource/sonarqube-scan-action
├── SONAR_HOST_URL: http://192.99.38.188:9000
└── SONAR_TOKEN: org secret (skipped if absent)
Deployment
- Stack:
services/sonarqube/docker-compose.yml— SonarQube LTS + dedicated PostgreSQL - Persistence: named Docker volumes (
sonarqube_data,sonarqube_logs,sonarqube_extensions,sonarqube_db) - System requirements: VPS must have
vm.max_map_count=524288(set via sysctl, persisted in/etc/sysctl.d/) - URL:
http://192.99.38.188:9000(orhttp://vpc.kronosb.com:9000once DNS resolves)
CI Integration
The sonar scan is added as a separate job that runs after test:
sonar:
needs: test
if: ${{ secrets.SONAR_TOKEN != '' }}
steps:
- uses: sonarsource/sonarqube-scan-action@v4
- uses: sonarsource/sonarqube-quality-gate-action@v1
Projects without SONAR_TOKEN skip the job silently — zero-impact principle preserved.
Consequences
Positive
- Centralized code quality dashboard for all Cornerstone-generated projects
- Security hotspot tracking aligned with ADR-0007
- Coverage trends visible over time
- Free tier covers all current and planned Cornerstone projects
Negative
- VPS must have sufficient RAM for SonarQube (minimum 2 GB available; recommended 4 GB)
vm.max_map_countrequires a one-time sysctl change on the host- Community Edition does not support branch analysis (only main branch + PRs via PR decoration)
Neutral
- SonarQube token must be provisioned manually per project (or as a GitHub org secret for shared use)
Implementation
- Server stack:
services/sonarqube/docker-compose.yml - Environment template:
services/sonarqube/.env.example - Scanner config (root):
sonar-project.properties - Scanner config (builder template):
builder/{{cookiecutter.project_slug}}/sonar-project.properties - CI step (builder template):
builder/{{cookiecutter.project_slug}}/.github/workflows/ci.yml - CI step (template):
{{cookiecutter.project_slug}}/.github/workflows/ci.yml