We've containerized our SonarQube scanner (Community Edition) for Kubernetes. The idea was sound: run the scanner as a Job, mount a PVC for the scanner cache to persist across runs, and speed up subsequent analyses. The reality is a mess.
The cache directory is mounted correctly (`/opt/sonarqube/.sonar/cache`), but the scanner acts like it's brand new on every Job execution. It redownloads everything. The logs show no errors, just the usual download spam. It defeats the entire purpose of the PVC.
Our scanner configuration is straightforward:
```yaml
apiVersion: batch/v1
kind: Job
spec:
template:
spec:
containers:
- name: sonar-scanner
image: sonarsource/sonar-scanner-cli:latest
volumeMounts:
- name: sonar-cache
mountPath: /opt/sonarqube/.sonar/cache
command: ["sonar-scanner"]
args:
- "-Dsonar.projectKey=$(PROJECT_KEY)"
- "-Dsonar.host.url=$(SONAR_HOST_URL)"
- "-Dsonar.login=$(SONAR_TOKEN)"
volumes:
- name: sonar-cache
persistentVolumeClaim:
claimName: sonar-scanner-cache-pvc
restartPolicy: Never
```
The PVC is ReadWriteMany. Permissions were a headache initially, but we've set the pod security context to run as non-root and confirmed the directory is writable.
The cache seems to reset between Jobs, even when the PVC is clearly populated from the prior run. It's like the scanner isn't reading from the directory it's writing to, or it's invalidating the cache based on some ephemeral metadata. Seen this before? The documentation is... optimistic about this working.
Trust but verify – and audit
That mount path looks correct, but the most common culprit in our setup was the user context inside the container. The scanner runs as a non-root user (sonarqube, I think?), and the PVC's default ownership is root:root.
Even if the directory is there, the scanner process can't write to it. Check the logs for any permission-denied errors, or exec into a completed pod and try to manually create a file in that directory as the same user.
Also, double-check that your Job's `backoffLimit` is set to 0. If it's retrying, a new pod is created and can sometimes have odd timing with volume attachments.
I agree about the ownership issue, but I'd refine the point about the user. The `sonarsource/sonar-scanner-cli` image doesn't run as a dedicated `sonarqube` user; the default user is UID 1001, which is a non-root user from the base image. The problem is the persistent volume's initial creation. If it's populated on the first run while the container runs as UID 1001, subsequent runs are fine. If the directory is pre-created or the PVC mounts an empty directory owned by root, that's when writes fail.
The fix we use is an init container in the Job pod to set ownership before the scanner starts. Something like:
```yaml
initContainers:
- name: volume-permission-fix
image: busybox
command: ['sh', '-c', 'chown -R 1001:1001 /opt/sonarqube/.sonar/cache']
volumeMounts:
- name: sonar-cache
mountPath: /opt/sonarqube/.sonar/cache
```
This sidesteps the need for privileged pods or custom security contexts.
Good catch on the UID 1001 detail, I'd missed that. That init container trick is solid.
One small nuance we found: if your PVC uses a dynamic provisioner like EBS or Azure Disk, you can sometimes bake the ownership into the storage class itself using mount options. But the init container route is way more portable across clusters.
Just make sure the init container mounts the cache directory at the same exact path, otherwise the chown command runs on a different filesystem slice. Your example has it right.
Data doesn't lie, but dashboards sometimes do.
Great point about checking `backoffLimit`. It's one of those silent killers. If it's not zero, the job controller might spin up a new Pod for a retry while the old Pod is still terminating, and the volume can get tangled up between them. We learned that the hard way with a database migration job.
A quick addition on the permission check: you can simulate the scanner's write attempt without waiting for a full run. Just `kubectl exec` into a pod that's using the same PVC mount and run `touch /opt/sonarqube/.sonar/cache/test.txt` as UID 1001. If that fails, the init container fix is definitely your next step.
api first
That init container is the right call. Just make sure you're chowning the parent directory the PVC mounts to, not a subdirectory that might not exist yet.
I'd run the chown command on `/opt/sonarqube/.sonar` in the init container. That way if the `cache` subdirectory doesn't exist on the first run, the scanner will create it with the correct UID when it starts. Chowning a non-existent `cache` path will fail silently.
Also, you can lock this down further by setting the scanner container's `securityContext.runAsUser` to 1001. It's redundant but clarifies intent in the spec.
Show me the bill