Code Quality Analysis Research — SonarQube CE vs SonarCloud¶
Status: Research Document Created: March 2026 Scope: Forma 3D Connect — Static Code Analysis & Quality Gates
Table of Contents¶
- Executive Summary
- Current State
- Codebase Size Analysis
- SonarQube Community Build Overview
- SonarQube CE — Container Deployment on Build Agent
- SonarQube CE — Resource Constraints
- SonarQube CE — Azure DevOps Pipeline Integration
- SonarQube CE — Backup to DigitalOcean Spaces
- SonarCloud — Cloud Alternative
- SonarCloud — Azure DevOps Pipeline Integration
- Lightweight Alternatives
- Nx Monorepo Scanner Integration
- Quality Gates & Quality Profiles
- Head-to-Head Comparison
- Cost Analysis
- Risks and Mitigations
- Recommendation
1. Executive Summary¶
This document evaluates options for continuous static code analysis for the Forma 3D Connect monorepo. Two primary options are compared: SonarQube Community Build (self-hosted on the DigitalOcean build agent) and SonarCloud Team (SaaS). Lightweight alternatives are also assessed.
Key Findings¶
| Aspect | SonarQube CE (self-hosted) | SonarCloud Team (SaaS) |
|---|---|---|
| Monthly cost | ~$49 (droplet upgrade) | ~$32 (LoC-based) |
| Branch / PR analysis | No | Yes |
| PR decoration (inline comments) | No | Yes |
| Infrastructure work | Significant | None |
| Maintenance | ~1 hr/month | Zero |
| Data sovereignty | Yes | No |
| Cost scales with LoC | No (flat) | Yes |
Recommendation¶
SonarCloud Team at $32/month is the pragmatic choice. It costs less than the self-hosted option, requires zero infrastructure, includes branch/PR analysis, and can be integrated into the existing Azure DevOps pipeline within an hour. Self-hosted only becomes cost-effective at 200k+ LoC.
2. Current State¶
2.1 Code Quality Tooling¶
| Tool | Purpose | Scope |
|---|---|---|
| ESLint | Linting (style + some code smells) | Per-project via Nx |
| TypeScript strict mode | Type safety | All projects |
| Syft + Grype | Container SBOM generation and CVE scanning | CI pipeline |
| Vitest / Jest | Unit test coverage | Frontend / Backend |
| Playwright + BDD | Acceptance tests | End-to-end |
2.2 What's Missing¶
- Cross-cutting code smell detection (duplicated code, cognitive complexity, dead code)
- Technical debt quantification in hours/days
- Unified quality dashboard with historical trends
- Quality gates that block merges based on measurable thresholds
- Security hotspot tracking beyond what Grype covers
2.3 Current Build Agent¶
| Spec | Value |
|---|---|
| vCPUs | 4 |
| RAM | 8 GB |
| Disk | 160 GB SSD |
| IP | 159.223.11.111 |
| Pool | DO-Build-Agents |
| Current role | Docker image packaging (2 Azure DevOps agent instances) |
3. Codebase Size Analysis¶
SonarCloud bills on ncloc (non-comment, non-blank lines) from source files. Test files marked via sonar.tests do not count toward billing.
3.1 Measured Lines of Code¶
| Area | Raw LoC (wc -l) |
ncloc (billable) |
|---|---|---|
| apps/web (React) | 17,403 | ~17,000 |
| apps/gateway | 3,168 | ~3,100 |
| apps/order-service | 27,021 | ~26,500 |
| apps/print-service | 12,038 | ~11,800 |
| apps/shipping-service | 8,893 | ~8,700 |
| apps/gridflock-service | 6,063 | ~5,900 |
| libs (shared) | 8,734 | ~8,500 |
| Production total | ~82k | ~80k |
| Test files (excluded) | ~57k | — |
3.2 SonarCloud Free Tier Fit¶
| Metric | Our Codebase | Free Tier Limit | Verdict |
|---|---|---|---|
| Production ncloc | ~80k | 50k | Exceeds by 60% |
| Users | <5 | 5 max | Fits |
The Free tier is not viable. At ~80k ncloc, the codebase exceeds the 50k limit. The Team plan ($32/month, up to 100k LoC) is required. The codebase is at ~80% of that 100k ceiling and will cross it as it grows.
4. SonarQube Community Build Overview¶
4.1 Language Support for Our Stack¶
| Technology | Supported | Notes |
|---|---|---|
| TypeScript | Yes | Through TS 5.6, 200+ rules |
| JavaScript | Yes | ECMAScript 3 through 2022 |
| React (JSX/TSX) | Yes | Framework-specific rules |
| NestJS | Yes | Analyzed via TypeScript rules |
| CSS / SCSS | Yes | Included |
| HTML | Yes | Included |
| Prisma / SQL | No | No .prisma file support |
4.2 Community Build vs Paid Editions¶
| Feature | Community Build | Developer ($) | Enterprise ($$) |
|---|---|---|---|
| Main branch analysis | Yes | Yes | Yes |
| Multi-branch analysis | No | Yes | Yes |
| Pull request analysis | No | Yes | Yes |
| PR decoration (comments) | No | Yes | Yes |
| Quality Gates | Yes | Yes | Yes |
| Webhooks & API | Yes | Yes | Yes |
| Security reports (OWASP/CWE) | No | No | Yes |
| SCA (dependency scanning) | No | Yes | Yes |
4.3 Elasticsearch — Mandatory and Non-Negotiable¶
SonarQube embeds Elasticsearch as a core architectural component. It cannot be disabled, replaced, or swapped for a lighter alternative.
Why it's mandatory: Elasticsearch powers the entire UI — issue browsing, project navigation, rule search, component tree, and measures dashboard. The database stores raw data; Elasticsearch indexes it for querying. Without it, SonarQube can ingest analysis results but cannot display them.
Architecture:
Scanner → Compute Engine → PostgreSQL → Elasticsearch → Web UI
Every component is required. When ES data is lost, SonarQube rebuilds it from PostgreSQL on startup — but ES must always be present at runtime.
Implications: This forces a hard minimum of ~4 GB RAM for SonarQube alone (ES heap + Web + Compute Engine + JVM overhead). There is no file-based mode, no SQLite option, and no plugin to swap ES.
5. SonarQube CE — Container Deployment on Build Agent¶
5.1 Target Architecture¶
5.2 Docker Compose Configuration¶
services:
sonarqube:
image: sonarqube:2026.1-community
container_name: sonarqube
restart: unless-stopped
depends_on:
sonarqube-db:
condition: service_healthy
environment:
SONAR_JDBC_URL: jdbc:postgresql://sonarqube-db:5432/sonarqube
SONAR_JDBC_USERNAME: sonarqube
SONAR_JDBC_PASSWORD: ${SONAR_DB_PASSWORD}
ports:
- "127.0.0.1:9000:9000"
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_logs:/opt/sonarqube/logs
- sonarqube_extensions:/opt/sonarqube/extensions
deploy:
resources:
limits:
memory: 4G
cpus: '1.0'
reservations:
memory: 2G
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9000/api/system/status | grep -q UP"]
interval: 30s
timeout: 10s
retries: 5
start_period: 120s
sonarqube-db:
image: postgres:16-alpine
container_name: sonarqube-db
restart: unless-stopped
environment:
POSTGRES_USER: sonarqube
POSTGRES_PASSWORD: ${SONAR_DB_PASSWORD}
POSTGRES_DB: sonarqube
volumes:
- sonarqube_postgresql:/var/lib/postgresql/data
deploy:
resources:
limits:
memory: 512M
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sonarqube"]
interval: 10s
timeout: 5s
retries: 5
volumes:
sonarqube_data:
sonarqube_logs:
sonarqube_extensions:
sonarqube_postgresql:
5.3 Kernel Tuning (Required)¶
vm.max_map_count=524288
fs.file-max=131072
5.4 Security¶
Port 9000 binds to 127.0.0.1 only — not exposed to the public internet. Azure DevOps agents access SonarQube at http://localhost:9000.
6. SonarQube CE — Resource Constraints¶
6.1 Why the Current Droplet Won't Work¶
SonarQube runs 3 JVM processes with individually tunable heaps:
| Process | Default Heap | Hard Minimum | Role |
|---|---|---|---|
| Web server | ~512 MB | ~256 MB | UI + API |
| Elasticsearch | ~512 MB | ~512 MB | Indexing (hard floor) |
| Compute Engine | ~512 MB | ~256 MB | Analysis processing |
| JVM overhead + OS | ~1.5 GB | ~1 GB | Metaspace, native memory |
| SonarQube total | ~3 GB | ~2 GB | Absolute minimum |
Current droplet memory budget:
6.2 Can Memory Be Reduced?¶
Theoretically, SonarQube can be squeezed to ~2 GB with aggressive JVM tuning:
sonar.web.javaOpts=-Xmx256m -Xms128m
sonar.search.javaOpts=-Xmx512m -Xms512m
sonar.ce.javaOpts=-Xmx256m -Xms128m
This is not recommended for an 82k LoC codebase — the Compute Engine may OOM during analysis, and the UI becomes sluggish. The official minimum for "up to 1M LoC" is 4 GB.
6.3 Verdict: Upgrade Required¶
| Spec | Current | Required |
|---|---|---|
| RAM | 8 GB | 16 GB |
| vCPUs | 4 | 4 (sufficient) |
| Monthly cost | ~$48 | ~\(96 (+\)48) |
7. SonarQube CE — Azure DevOps Pipeline Integration¶
7.1 Pipeline Flow¶
7.2 Pipeline Changes Required¶
A new CodeQuality stage in azure-pipelines.yml:
- stage: CodeQuality
displayName: 'Code Quality Analysis'
dependsOn: ValidateAndTest
condition: and(succeeded(), eq(variables.isMain, true))
pool:
name: 'DO-Build-Agents'
jobs:
- job: SonarQubeAnalysis
displayName: 'SonarQube Analysis'
steps:
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- script: |
npx sonar-scanner \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.token=$(SONAR_TOKEN) \
-Dsonar.projectKey=forma3d-connect \
-Dsonar.sources=apps,libs \
-Dsonar.tests=apps,libs \
-Dsonar.test.inclusions=**/*.spec.ts,**/*.spec.tsx,**/*.test.ts \
-Dsonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/** \
-Dsonar.typescript.lcov.reportPaths=coverage/**/lcov.info
displayName: 'Run SonarQube Scanner'
- script: |
RESULT=$(curl -sf -u $(SONAR_TOKEN): \
"http://localhost:9000/api/qualitygates/project_status?projectKey=forma3d-connect" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['projectStatus']['status'])")
echo "Quality Gate: $RESULT"
if [ "$RESULT" != "OK" ]; then
echo "##vso[task.logissue type=error]Quality Gate FAILED"
exit 1
fi
displayName: 'Check Quality Gate'
Additional requirements:
- Add SONAR_TOKEN as a secret pipeline variable in Azure DevOps
- Add sonar-project.properties to the repository root
- Deploy Docker Compose stack on build agent
- Upgrade droplet to 16 GB
- Apply kernel tuning
Only works on main branch — Community Build cannot analyze feature branches or PRs.
8. SonarQube CE — Backup to DigitalOcean Spaces¶
8.1 What Needs Backup¶
| Component | Needs Backup | Notes |
|---|---|---|
| PostgreSQL database | Yes | All projects, issues, metrics, settings (~50–200 MB) |
| Elasticsearch data | No | Rebuilt from PostgreSQL on startup |
Plugins (extensions/) |
Optional | Can be reinstalled |
8.2 Backup Flow¶
8.3 s3cmd Configuration¶
The build agent already uses DO Spaces credentials (DO_SPACES_KEY, DO_SPACES_SECRET, DO_SPACES_BUCKET, DO_SPACES_REGION=ams3). The existing .s3cfg can be reused.
8.4 Restore Procedure¶
- Stop SonarQube
- Download and decompress dump from DO Spaces
pg_restoreinto the sonarqube-db container- Clear Elasticsearch data directory
- Start SonarQube — ES reindexes automatically (~5–10 minutes)
9. SonarCloud — Cloud Alternative¶
9.1 Why Consider SonarCloud¶
SonarCloud is the SaaS version of SonarQube, hosted by Sonar. It eliminates all infrastructure concerns while adding features that Community Build lacks.
9.2 Plan Fit¶
| SonarCloud Plan | LoC Limit | Users | Branch/PR | Monthly Cost | Our Fit |
|---|---|---|---|---|---|
| Free | 50k | 5 | Yes | $0 | Too small — we're at ~80k |
| Team | 100k | Unlimited | Yes | $32 | Fits now — 80% of ceiling |
| Enterprise | Custom | Unlimited | Yes | Custom | Overkill for current scale |
9.3 What Team Tier Includes Over Free¶
- Unlimited users
- AI CodeFix (automated vulnerability remediation)
- Improved secrets detection
- Unlimited public project scanning
- 30+ languages and frameworks
- Main branch and pull request analysis
- DevOps platform integration (Azure DevOps, GitHub, etc.)
- Commercial support available
9.4 Growth Projection¶
The codebase is at ~80k ncloc and growing. When it crosses 100k, the next LoC tier kicks in (pricing not publicly listed, estimated ~$50–60/month). The self-hosted option stays flat regardless of codebase size.
10. SonarCloud — Azure DevOps Pipeline Integration¶
10.1 Integration Architecture¶
10.2 One-Time Setup¶
Step 1 — Install the Azure DevOps Extension
Install the SonarCloud extension from the Visual Studio Marketplace into the Azure DevOps organization.
Step 2 — Create SonarCloud Organization
- Sign up at sonarcloud.io with the Azure DevOps account
- Import the Azure DevOps organization
- Create the
forma3d-connectproject - Select the Team plan ($32/month)
Step 3 — Create Service Connection
In Azure DevOps: Project Settings > Service connections > New > SonarCloud
- Generate a token in SonarCloud (User > My Account > Security)
- Name the service connection
SonarCloud
10.3 Pipeline Changes Required¶
Add a new job to the existing ValidateAndTest stage in azure-pipelines.yml:
# Inside the ValidateAndTest stage, alongside Lint, TypeCheck, UnitTests
- job: CodeQuality
displayName: 'Code Quality (SonarCloud)'
cancelTimeoutInMinutes: 0
steps:
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- task: SonarCloudPrepare@4
displayName: 'Prepare SonarCloud Analysis'
inputs:
SonarCloud: 'SonarCloud'
organization: 'forma3d'
scannerMode: 'cli'
configMode: 'manual'
cliProjectKey: 'forma3d_forma3d-connect'
cliProjectName: 'Forma 3D Connect'
cliSources: 'apps/web/src,apps/gateway/src,apps/order-service/src,apps/print-service/src,apps/shipping-service/src,apps/gridflock-service/src,libs'
extraProperties: |
sonar.tests=apps,libs
sonar.test.inclusions=**/*.spec.ts,**/*.spec.tsx,**/*.test.ts
sonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/**,**/*.spec.ts,**/*.spec.tsx,**/*.test.ts
sonar.typescript.lcov.reportPaths=coverage/**/lcov.info
sonar.javascript.lcov.reportPaths=coverage/**/lcov.info
- task: SonarCloudAnalyze@4
displayName: 'Run SonarCloud Analysis'
- task: SonarCloudPublish@4
displayName: 'Publish Quality Gate Result'
inputs:
pollingTimeoutSec: '300'
10.4 What Changes vs Current Pipeline¶
| Change | Scope | Effort |
|---|---|---|
| Install SonarCloud extension in Azure DevOps | One-time, org-level | 2 minutes |
| Create SonarCloud service connection | One-time, project-level | 5 minutes |
Add CodeQuality job to ValidateAndTest stage |
azure-pipelines.yml |
~15 lines of YAML |
| No infrastructure changes | — | None |
| No droplet upgrade | — | None |
| No kernel tuning | — | None |
| No Docker Compose stack | — | None |
| No backup script | — | None |
Total setup time: ~30 minutes
10.5 PR Decoration¶
With SonarCloud Team, PRs targeting main (or develop) automatically receive:
- Quality gate status as a PR check
- Inline comments on new issues directly in the Azure DevOps PR view
- Summary of new bugs, vulnerabilities, code smells, and coverage
This works out of the box after enabling the Azure DevOps integration in SonarCloud project settings.
10.6 Coverage Reports¶
For SonarCloud to display coverage data, the test job needs to run before the SonarCloud analysis. Two approaches:
Option A — Depend on UnitTests job (recommended):
- job: CodeQuality
displayName: 'Code Quality (SonarCloud)'
dependsOn: UnitTests
steps:
# Download coverage artifacts from UnitTests job
- task: DownloadPipelineArtifact@2
inputs:
source: 'current'
artifact: 'coverage'
path: '$(System.DefaultWorkingDirectory)/coverage'
# ... SonarCloud tasks ...
Option B — Run tests inside the CodeQuality job (simpler but slower):
- job: CodeQuality
displayName: 'Code Quality (SonarCloud)'
steps:
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- script: pnpm nx run-many --target=test --all --parallel=2 -- --coverage
displayName: 'Run Tests with Coverage'
# ... SonarCloud tasks ...
11. Lightweight Alternatives¶
11.1 Alternatives Evaluated¶
No lightweight tool matches SonarQube/SonarCloud's combination of persistent dashboard, historical trends, technical debt quantification, and quality gates. Here's what exists:
| Tool | Type | Self-Hosted | RAM | TS/JS | Dashboard | PR Analysis | Cost |
|---|---|---|---|---|---|---|---|
| Semgrep CE | CLI | Partial | ~200 MB | Yes | Cloud only (paid) | Yes | Free (CE) |
| MegaLinter | CI-only | Yes | ~500 MB | Yes | No | Via CI | Free |
| FTA | CLI | Yes | ~0 | TS only | No | No | Free |
| jscpd | CLI | Yes | ~0 | Yes | No | No | Free |
| Qodana (JetBrains) | Server | Yes | 16 GB | Paid only | Yes | Yes | $$$ |
| CodeAnt AI | SaaS | No | 0 | Yes | Yes | Yes | Paid |
11.2 Why They Fall Short¶
Semgrep CE — The most interesting lightweight option. Runs as a single container (~200 MB), 3000+ rules, supports TypeScript. But the free version is CLI-only with JSON/SARIF output — no persistent dashboard, no historical trends, no quality gates with memory. The web dashboard requires the paid Semgrep AppSec Platform.
MegaLinter — Wraps 100+ linters (ESLint, jscpd, Semgrep) into a single Docker run. Zero server infrastructure. But produces a one-time report per pipeline run — no trend tracking, no unified dashboard.
FTA (Fast TypeScript Analyzer) — Rust-based, runs via npx fta-cli, measures complexity and maintainability. But TS-only, no dashboard, no duplication detection, no quality gates.
Qodana (JetBrains) — Full-featured but requires 16 GB RAM minimum and the TypeScript/JavaScript analyzer is only available in the paid tier. Heavier than SonarQube.
11.3 Composable "DIY" Stack¶
It's possible to combine ESLint + jscpd + FTA + Semgrep CE for zero-cost analysis in CI. The catch: no unified dashboard, no technical debt tracking, no historical trends, and you maintain the glue code. This gives "better than nothing" without the value of a real code quality platform.
11.4 Complementary Tooling¶
Since neither SonarQube CE nor SonarCloud includes full security scanning, the existing Syft + Grype integration remains essential:
| Capability | SonarQube/Cloud | Syft + Grype | Combined |
|---|---|---|---|
| Code smells / complexity | Yes | No | Yes |
| SAST | Basic | No | Basic |
| CVE scanning (containers) | No | Yes (Grype) | Yes |
| SBOM generation | No | Yes (Syft) | Yes |
12. Nx Monorepo Scanner Integration¶
12.1 Root-Level Scan (Recommended)¶
A sonar-project.properties file at the repository root, used by both SonarQube CE and SonarCloud:
sonar.projectKey=forma3d-connect
sonar.projectName=Forma 3D Connect
# Sources
sonar.sources=apps/web/src,apps/gateway/src,apps/order-service/src,apps/print-service/src,apps/shipping-service/src,apps/gridflock-service/src,libs
sonar.tests=apps,libs
sonar.test.inclusions=**/*.spec.ts,**/*.spec.tsx,**/*.test.ts
# Exclusions
sonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/**,**/*.spec.ts,**/*.spec.tsx,**/*.test.ts,**/test/**,**/__mocks__/**
# Coverage
sonar.typescript.lcov.reportPaths=coverage/**/lcov.info
# Encoding
sonar.sourceEncoding=UTF-8
12.2 Per-Project Scan (Alternative)¶
The @koliveira15/nx-sonarqube plugin adds a sonar target per Nx project, leveraging the dependency graph:
pnpm add -D @koliveira15/nx-sonarqube
pnpm nx g @koliveira15/nx-sonarqube:config gateway
pnpm nx sonar gateway
| Approach | Pros | Cons |
|---|---|---|
| Root-level scan | Single analysis, simple setup | No per-project isolation |
| Nx plugin per-project | Granular metrics per service | More config, multiple SonarQube/Cloud projects |
Recommendation: Start with root-level for simplicity.
12.3 Coverage Flow¶
13. Quality Gates & Quality Profiles¶
13.1 Default Quality Gate ("Sonar Way")¶
| Metric | Condition |
|---|---|
| New code coverage | ≥ 80% |
| New duplicated lines | ≤ 3% |
| New maintainability rating | A |
| New reliability rating | A |
| New security rating | A |
| New security hotspots reviewed | 100% |
13.2 Proposed Custom Gate¶
| Metric | Threshold | Rationale |
|---|---|---|
| New code coverage | ≥ 60% | Achievable starting point; raise over time |
| New duplicated lines | ≤ 5% | Slightly relaxed for initial adoption |
| New bugs | 0 | Zero tolerance |
| New vulnerabilities | 0 | Zero tolerance |
| New code smells | ≤ 10 | Allow some while ramping up |
| New security hotspots reviewed | 100% | All hotspots must be triaged |
13.3 SonarLint Integration¶
SonarLint (VS Code / Cursor extension) can connect to both SonarCloud and self-hosted SonarQube in "Connected Mode", syncing quality profiles and rules for real-time IDE feedback.
14. Head-to-Head Comparison¶
| Criterion | SonarQube CE (self-hosted) | SonarCloud Team (SaaS) |
|---|---|---|
| Monthly cost | ~$49 (droplet upgrade) | $32 (at 100k LoC) |
| Setup effort | ~1 day | ~30 minutes |
| Infrastructure | Docker Compose + PostgreSQL + kernel tuning | None |
| Maintenance | ~1 hr/month (updates, backups, monitoring) | Zero |
| Branch analysis | No (main only) | Yes (all branches) |
| PR decoration | No | Yes (inline comments) |
| PR quality gate | No | Yes (blocks merge) |
| AI CodeFix | No | Yes |
| Dashboard | Self-hosted (localhost:9000) | sonarcloud.io |
| Data sovereignty | Yes (DO droplet + Spaces) | No (Sonar's cloud) |
| Cost at 200k LoC | Still $49 | ~$50–60 |
| Cost at 500k LoC | Still $49 | Higher |
| Backup needed | Yes (pg_dump + s3cmd) | No |
| SonarLint sync | Yes | Yes |
| Nx monorepo support | Yes | Yes |
15. Cost Analysis¶
15.1 Monthly Cost Comparison¶
| Item | SonarQube CE | SonarCloud Team |
|---|---|---|
| License / subscription | $0 | $32 |
| Droplet upgrade (8→16 GB) | $48 | $0 |
| DO Spaces backup storage | ~$1 | $0 |
| Maintenance effort (~1 hr) | ~$50 (time value) | $0 |
| Total | ~$99/month | $32/month |
15.2 Break-Even Analysis¶
SonarCloud costs scale with LoC. Self-hosted costs are fixed. At current growth rates:
| Codebase Size | SonarCloud (est.) | SonarQube CE | Cheaper Option |
|---|---|---|---|
| 80k (now) | $32/mo | $99/mo | SonarCloud |
| 100k | ~$32/mo | $99/mo | SonarCloud |
| 200k | ~$50–60/mo | $99/mo | SonarCloud |
| 500k | ~$100+/mo | $99/mo | Break-even |
| 1M+ | $150+/mo | $99/mo | SonarQube CE |
Self-hosted only becomes cost-effective well past 500k LoC — likely years away at current growth pace.
16. Risks and Mitigations¶
16.1 SonarCloud Risks¶
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Cost increases as codebase grows | High | Low | Budget for next tier; switch to self-hosted if needed |
| SonarCloud outage blocks CI | Low | Medium | Set quality gate to advisory mode initially |
| Data leaves the security perimeter | Ongoing | Low | Source snippets only; no secrets in code |
| Vendor lock-in | Low | Low | sonar-project.properties is portable to self-hosted |
16.2 SonarQube CE Risks¶
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Build agent OOM | Medium | High | Upgrade droplet to 16 GB |
| Update breaks compatibility | Low | Medium | Pin to LTA releases |
| Backup fails silently | Medium | Medium | Log + Uptime Kuma health check |
| No PR feedback loop | Ongoing | Medium | SonarLint Connected Mode for IDE feedback |
| Disk fills up | Low | Medium | Docker log rotation + SonarQube housekeeping |
17. Recommendation¶
17.1 Verdict: SonarCloud Team¶
Go with SonarCloud Team at $32/month because:
- Cheaper — \(32 vs ~\)99/month total cost of self-hosted (including time)
- More features — Branch analysis, PR decoration, AI CodeFix
- Zero infrastructure — No droplet upgrade, no Docker Compose, no kernel tuning, no backups
- 30-minute setup — Install extension, create service connection, add ~15 lines to
azure-pipelines.yml - Portable — If the codebase outgrows the pricing, migrate to self-hosted SonarQube using the same
sonar-project.properties
17.2 Implementation Steps¶
- Sign up at sonarcloud.io with the Azure DevOps organization
- Create the
forma3d-connectproject, select Team plan - Install the SonarCloud extension in Azure DevOps
- Create a
SonarCloudservice connection in Azure DevOps project settings - Add
sonar-project.propertiesto the repository root - Add the
CodeQualityjob to theValidateAndTeststage inazure-pipelines.yml - Run the pipeline, review the initial dashboard
- Configure a custom quality gate (start with advisory mode)
- Set up SonarLint Connected Mode for the team
- Formalize as an ADR
17.3 Decision Needed¶
- Approve SonarCloud Team subscription — $32/month
- Advisory vs blocking — Should the quality gate block PRs from the start, or run in advisory mode first?
- Coverage dependency — Should the CodeQuality job wait for UnitTests to finish (accurate coverage) or run independently (faster)?
This document should be formalized as an ADR once a decision is made.