INITIALIZING
c#

Local SonarQube + .NET! Catch Bugs Before They Catch You!!!

Local SonarQube + .NET! Catch Bugs Before They Catch You!!!

If you've been coding in C# for a while, you've probably run into that dreaded moment during a pull request:

“Hey, can you add unit tests?”

“Why are there magic strings?”

“You missed a null check here…”

Wouldn't it be nice if a robot pointed out those things before your teammates did, so the review comments arrive with slightly less judgment in them, and slightly more “the machine already told me, please stop”? Enter SonarQube, a fantastic tool for static analysis and continuous inspection of your codebase, and a genuinely excellent buffer between “code I wrote at 11pm” and “code a human being will silently judge.”

Today, we'll cover:

  • Running SonarQube locally with Docker Compose
  • Setting up a manual project in the SonarQube UI
  • Using the .NET CLI (dotnet sonarscanner) to analyze your project alongside dotnet build
  • Integration tips for Visual Studio and VS Code
  • Bonus: running SonarQube on your HomeLab Raspberry Pi
  • A PowerShell alias trick, plus NTFY push notifications when a scan finishes, so your phone gets to participate in your shame or triumph in real time

Docker Compose for SonarQube

Create a file named docker-compose.yml:

version: "3.9"

services:
  sonarqube:
    image: sonarqube:community
    container_name: sonarqube
    ports:
      - "9000:9000"
    environment:
      SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: "true"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_logs:/opt/sonarqube/logs
      - sonarqube_extensions:/opt/sonarqube/extensions

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
      POSTGRES_DB: sonarqube
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_logs:
  sonarqube_extensions:
  postgres_data:

Run it with:

docker compose up -d

Open http://localhost:9000, log in with admin/admin — and actually change that password immediately, don't be the reason your own homelab has to sit you down for a talk.

Setting Up a Manual Project in SonarQube

Inside the SonarQube dashboard:

  1. Go to Projects → Create Project.
  2. Choose Manually, because “automatically” involves connecting a whole Git provider and we are not here for that today.
  3. Enter your project name, e.g., MyDotnetApp.
  4. Generate a project token — copy it, we'll need it in a moment, and treat it like a password, because functionally, it is one, and functionally, you will absolutely forget you copied it if you don't paste it somewhere safe right now.

That's it. SonarQube is ready to accept scans, and mildly ready to accept your codebase's flaws.

Wiring the CLI with .NET Build

Install the scanner:

dotnet tool install --global dotnet-sonarscanner

Navigate to your solution folder and run:

dotnet sonarscanner begin `
  /k:"MyDotnetApp" `
  /d:sonar.token="YOUR_PROJECT_TOKEN" `
  /d:sonar.host.url="http://localhost:9000"

dotnet build --no-incremental --disable-build-servers

dotnet sonarscanner end /d:sonar.token="YOUR_PROJECT_TOKEN"

Two things worth flagging if you're following an older tutorial (including, awkwardly, an earlier draft of this exact post — glass houses, etc.): sonar.login still technically works, but sonar.token is the currently recommended parameter for anything running scanner 5.13 or newer. Same idea, better name, arguably a small act of self-respect. And dotnet build gets the --no-incremental --disable-build-servers flags here on purpose: incremental builds and background build servers happily skip recompiling files that haven't changed, which means SonarScanner never even sees them, and your beautifully clean report is lying to you by omission, with a smile. A few extra seconds of build time is a small price for actually analyzing your whole codebase instead of whatever subset felt like showing up.

Head back to the SonarQube dashboard, and real metrics will be waiting for you: code smells, security hotspots, duplicated code blocks, and a running, itemized tally of exactly how much technical debt you've been quietly, cheerfully accumulating while insisting everything was “fine.”

Visual Studio & VS Code Integration

  • Visual Studio: Install the SonarLint extension, connect to SonarQube, and bind your solution.
  • VS Code: Install the SonarLint extension, run “SonarLint: Connect to SonarQube” from the command palette, and bind your workspace.

Both editors now surface SonarQube issues live as you type, using the exact same rules as the server — so you find out about the magic string while you're still typing it, not three days later in a PR comment with a passive-aggressive “just curious why we're doing it this way 👀.”

Bonus: Running SonarQube in a HomeLab (Raspberry Pi)

If you're into tinkering (and I know many of you are, this blog has a type), SonarQube doesn't need to stay tied to localhost like it's afraid of commitment.

With a HomeLab setup — say, Docker running on a Raspberry Pi 5 — you can deploy SonarQube to the Pi and let it serve every device on your network instead of just the one laptop that happens to be running it today, alone, like it's the only one that matters.

Adjust sonar.host.url in your CLI calls from http://localhost:9000 to http://raspberrypi.local:9000 (or the Pi's actual IP), and point your VS Code and Visual Studio SonarLint plugins at the same URL. Now your entire dev environment — laptops, desktops, even teammates connecting in from elsewhere — talks to one single, persistent SonarQube instance, instead of everyone quietly running their own disconnected copy and comparing notes never.

This is especially handy if you're already running other services on your Pi (Git, Pi-hole, Grafana dashboards — the greatest hits of homelab self-hosting). SonarQube fits right into that ecosystem instead of demanding its own dedicated box like a diva.

Gotchas & Tips

  • Performance: the first scan is always the slowest — caching speeds up everything after, like the tool is warming up its opinions.
  • Resource needs: on a Raspberry Pi, give SonarQube enough memory and swap, or it will simply decline to start, no negotiation, no explanation, just silence.
  • CI/CD: once stable locally, wire it into GitHub Actions or Azure DevOps to actually enforce quality gates instead of just politely suggesting them and hoping for the best.
  • CLI aliases in PowerShell: tired of typing the entire scanner incantation every single time like you're casting a spell? Wrap it in a function and cast it properly, once.

PowerShell Alias with Raspberry Pi Support + NTFY Notification

# Alias to wrap SonarScanner for .NET with default parameters
function sonar-scan {
    param(
        [string]$ProjectKey = "MyDotnetApp",
        [string]$Version = "1.0.0",
        [switch]$UsePi,     # toggle if you want to target Raspberry Pi instance
        [string]$NotifyUrl = "https://ntfy.sh/my-sonarqube"  # replace with your ntfy topic
    )

    $url = "http://localhost:9000"
    if ($UsePi) {
        # Change raspberrypi.local to your Pi's hostname or IP
        $url = "http://raspberrypi.local:9000"
    }

    try {
        dotnet sonarscanner begin `
          /k:$ProjectKey `
          /v:$Version `
          /d:sonar.host.url=$url `
          /d:sonar.token="YOUR_PROJECT_TOKEN"

        dotnet build --no-incremental --disable-build-servers

        dotnet sonarscanner end /d:sonar.token="YOUR_PROJECT_TOKEN"

        # Send NTFY success notification
        Invoke-RestMethod -Uri $NotifyUrl -Method POST -Body "✅ SonarQube scan finished successfully for $ProjectKey v$Version"
    }
    catch {
        # Send NTFY failure notification
        Invoke-RestMethod -Uri $NotifyUrl -Method POST -Body "❌ SonarQube scan FAILED for $ProjectKey v$Version"
        throw
    }
}

You can use NTFY to tell you when it's done — I covered the full setup in this earlier post.

Usage:

# Localhost scan
sonar-scan -ProjectKey "MyDotnetApp" -Version "1.2.3"

# Raspberry Pi HomeLab scan + notification
sonar-scan -ProjectKey "MyDotnetApp" -Version "1.2.3" -UsePi

Every time the scan finishes, your phone buzzes with a push notification — no more babysitting a terminal window like it owes you an explanation it was never going to give. 🚀

Architecture Flow

The whole flow, in words instead of a picture this time: the developer runs dotnet sonarscanner from the CLI (either the raw commands above or the PowerShell alias doing the typing for you), which sends analysis results to SonarQube — running either on localhost or on the Raspberry Pi, depending on which flavor of homelab commitment you've chosen today. SonarQube stores and scores that analysis, and the same rule set gets synced back down into your IDE via SonarLint, so Visual Studio and VS Code are always working off the identical rulebook the server itself uses, not some locally cached approximation of it that drifted out of sync six months ago. One source of truth, three different places you can bump into it, zero excuses for “well my editor didn't flag it.”

And here's the part I'm genuinely excited about: this entire flow — scanner, results, quality gates, the whole pipeline — is exactly the kind of structured, machine-readable feedback loop that an AI agent thrives on. Instead of a human squinting at a dashboard, imagine an agent that runs the scan, reads the results straight off SonarQube's API, and either fixes the flagged issues itself or refuses to let a build proceed until the quality gate actually passes — no human babysitting required, just a robot holding another robot's code to a standard. I'm already circling back to MCP for exactly this reason, and this SonarQube setup is quietly becoming one of the first tools I plan to hand an agent the keys to. More on that soon.

Conclusion

With Docker Compose on your laptop or a Raspberry Pi in your HomeLab, SonarQube becomes a personal code quality dashboard, not a corporate tool you only meet during code review. Add the IDE integrations, the PowerShell alias, and push notifications, and you've got a smooth, developer-friendly workflow that keeps your codebase honest before it ever has the chance to embarrass you in a pull request — and, soon enough, before an AI agent even gets a chance to judge it first.

Happy coding!!!