Chapter 04Lesson 01~115 minutes

Docker, Containers, Persistent Storage, and Production Deployment: Core Concepts and Mental Model

Understand what a SonarQube container owns—and what it does not—so image identity, database state, search storage, configuration, secrets, networking, host limits, and logs remain explicit operational boundaries.

Docker imagePersistencePostgreSQLNetworksHost limits

Learning objectives

  • Trace a containerized SonarQube request from pinned image and runtime configuration to web/compute/search processes and durable database state.
  • Separate image layers, container writable state, named volumes, database volumes, logs, and temporary search state.
  • Explain why container networking uses service/container DNS names rather than host-style localhost assumptions.
  • Inspect image tags/digests, container user, mounts, environment, networks, resource state, host limits, logs, and health without mutating the stack.
  • Explain why containers do not remove database, host-kernel, secret-management, upgrade, or least-privilege responsibilities.

1. Current container baseline: packaging is not architecture

This chapter uses SonarQube Community Build 26.9.0.129388 and the official image sonarqube:26.9.0.129388-community. Docker Hub currently maps the Community Build tag to multi-platform index digest sha256:62930c7f510534bb2bf551ca69ad3ee6f8e12b4116d394597c64cefccbb3313b. A tag is a human-readable selection; a digest is the stronger content identity. Record both.

Do not use latest as a production change policy. Floating tags can move. A controlled deployment records an exact release tag and the resolved digest, reviews update notes, verifies plugins/database compatibility, and changes image identity deliberately.

Containers package SonarQube binaries and its Java runtime, but the instance still depends on a supported database, embedded-search host prerequisites, writable paths, a network path to the database, secure configuration, resource headroom, and a deliberate upgrade/recovery plan.

2. Mental model: six boundaries around one SonarQube container

Start from ownership rather than from a docker run command. The container is replaceable compute. Durable product state is not “inside Docker” as one undifferentiated thing.

Containerized SonarQube ownership model

Every arrow crosses a configuration, persistence, network, or trust boundary that must be observable independently.

flowchart TD
I[Pinned SonarQube image
tag + digest] --> C[SonarQube container]
CFG[Runtime configuration
JDBC + web + JVM options] --> C
SEC[Secret source
DB password / JWT / tokens] --> C
HOST[Container host
CPU RAM disk kernel limits] --> C
C --> V[Named volumes
data / extensions / logs]
C --> TMP[Writable temp
ephemeral scratch]
C --> N[Private Docker network]
N --> DB[(PostgreSQL
durable application state)]
C --> WEB[Web :9000]
C --> CE[Compute Engine]
C --> ES[Embedded search]
WEB --> DB
CE --> DB
ES --> V

The image supplies versioned application bits. Runtime configuration selects database and server behavior. Named volumes preserve selected filesystem state across container replacement. The database is the durable source of SonarQube application state. Embedded search still inherits host-level constraints. The network resolves other containers by service/container name. Each boundary fails differently.

3. Persistence model: database first, then supporting volumes

State Typical location Durability role Do not confuse it with
Application state External PostgreSQL/SQL Server/Oracle Primary durable projects, issues, users, settings, history Search indexes or image layers
Search/index data /opt/sonarqube/data Persistent operational index state; fast local-volume semantics matter A supported database backup
Extensions /opt/sonarqube/extensions Plugins and Oracle JDBC driver when applicable Bundled language analyzers or durable project data
Logs /opt/sonarqube/logs First-failure and operational evidence Monitoring/backup by themselves
Temp /opt/sonarqube/temp and /tmp Writable runtime scratch; Elasticsearch 8.x requires writable /tmp A durable state store
Database files in lab PostgreSQL named volume Durable DB state across container replacement SonarQube data volume

Current SonarSource Docker preparation guidance explicitly creates named volumes for data, logs, and extensions, and warns against casually deleting database volumes. The image also declares its temp path as a volume, but temp is writable scratch rather than the durable source of project history.

4. Image identity: tag, platform manifest, and runtime user

Read-only image inspection should happen before starting the application:

docker pull sonarqube:26.9.0.129388-community
docker image inspect sonarqube:26.9.0.129388-community --format '{{json .RepoDigests}}'
docker image inspect sonarqube:26.9.0.129388-community --format 'user={{.Config.User}} ports={{json .Config.ExposedPorts}} volumes={{json .Config.Volumes}}'

The current official image runs as the non-root sonarqube user (UID 1000 in the published image build), exposes TCP 9000, and defines SonarQube paths under /opt/sonarqube. Those facts matter when you choose bind mounts or security policies: a host directory writable only by root can make the container fail even though the image itself is valid.

Evidence rule: retain the observed RepoDigests value from your environment. Do not assume an architecture-specific manifest digest from someone else’s machine.

5. Runtime configuration and secrets are different concerns

For Docker installations, SonarSource prefers system properties as environment variables. The JDBC boundary uses SONAR_JDBC_URL, SONAR_JDBC_USERNAME, and SONAR_JDBC_PASSWORD. That does not mean a production password belongs in a Dockerfile, Git repository, copied shell history, or screenshot.

# Example names only; never commit a real production value.
SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME=sonar
SONAR_JDBC_PASSWORD=FAKE_LOCAL_LAB_PASSWORD

A Dockerfile is build-time provenance and should remain reusable. Environment injection is runtime configuration, but Docker daemon administrators can inspect container configuration, so production secret handling must use the organization’s supported secret-management/orchestration boundary and least-privilege daemon access. This chapter does not invent an unsupported *_FILE convention.

6. Container DNS: why localhost is usually the wrong database host

Inside the SonarQube container, localhost means that same SonarQube container. A PostgreSQL process in a different container is reached through the private network using its container/service DNS name, for example db.

Correct: jdbc:postgresql://db:5432/sonar
Wrong for a separate DB container: jdbc:postgresql://localhost:5432/sonar

Publishing PostgreSQL port 5432 to the host is unnecessary for the mandatory lab. Only SonarQube’s loopback-bound web port needs host exposure. Keeping the database private reduces accidental reachability and makes the trust boundary visible.

7. Containers inherit host constraints

Embedded Elasticsearch still uses the Linux kernel underneath the container runtime. On a native Linux Docker host, current SonarQube guidance requires at least vm.max_map_count=524288, fs.file-max=131072, ulimit -n 131072, and ulimit -u 8192. Elasticsearch 8.x also needs read/write access to /tmp.

# Read-only Linux-host preflight
sysctl vm.max_map_count
sysctl fs.file-max
ulimit -n
ulimit -u
docker info --format 'CPUs={{{{.NCPU}}}} Memory={{{{.MemTotal}}}}'

With Docker Desktop on Windows/macOS, a managed Linux VM owns these kernel details. Do not “fix” a bootstrap failure by disabling Elasticsearch checks. Verify the current platform-specific runtime guidance or use a suitable Linux VM/host for a production-shaped exercise.

8. Read-only container inspection before any redeploy

docker ps --filter name=sq-lab --no-trunc
docker inspect sq-lab --format 'image={{{{.Image}}}} user={{{{.Config.User}}}}'
docker inspect sq-lab --format '{{{{json .Mounts}}}}'
docker inspect sq-lab --format '{{{{json .NetworkSettings.Networks}}}}'
docker logs --tail 120 sq-lab
docker stats --no-stream sq-lab

These commands answer different questions: which image object is running, which user the image requests, which mounts survived container creation, which networks/DNS scope the container joined, what first-failure evidence exists, and whether the container host is resource-starved.

9. Foundation mistakes to eliminate early

  • “The container is the backup.” It is replaceable compute, not a database recovery artifact.
  • “Named volumes make every file durable.” Only mounted paths persist; external database state has its own volume/backup lifecycle.
  • “localhost means my Docker host or sibling DB.” Inside a container it means that container.
  • “latest is convenient and therefore safe.” It hides change identity and can make rollback analysis ambiguous.
  • “Docker solved the Elasticsearch host limits.” Containers still share/inherit host kernel constraints.
  • “Putting a password in ENV makes it secret.” Runtime environment is configuration; production secret controls need a deliberate trust boundary.

10. Why this matters in DevOps

A production deployment must be reproducible enough that an operator can answer: which image digest ran, which database it used, which volumes were mounted, which configuration source supplied JDBC settings, which host constraints were satisfied, and which logs prove the first healthy/failed start. That is the container equivalent of the evidence chain introduced in Chapters 01–03.

Knowledge check

Why is the PostgreSQL database a separate persistence boundary from sonarqube_data?

Why is jdbc:postgresql://localhost:5432/sonar normally wrong when PostgreSQL is another container?

What does an image digest add beyond an exact version tag?

Does running SonarQube in Docker remove Linux vm.max_map_count requirements?

Why should temp be writable but not treated as durable business state?

Next lesson

From architecture to one reproducible local stack

Lesson 2 constructs an isolated SonarQube + PostgreSQL lab, records image/network/volume evidence, recreates the SonarQube container, and proves which state survives.

Official references and version notes

Version and compatibility note

Version-sensitive statements were rechecked against current SonarSource and Docker Hub primary material on 2026-09-07. Mandatory examples use SonarQube Community Build 26.9.0.129388 with the official sonarqube:26.9.0.129388-community image and PostgreSQL postgres:17.11, which is inside the currently supported PostgreSQL 14–18 range. The recorded multi-platform SonarQube image index digest at authoring time is sha256:62930c7f510534bb2bf551ca69ad3ee6f8e12b4116d394597c64cefccbb3313b; learners should inspect the digest they actually pull and retain it in their evidence packet. The SonarQube image supplies its own Java runtime; record java -version inside the running container when runtime provenance matters. No SonarScanner execution, third-party plugin, CI/provider integration, enterprise identity provider, or commercial feature is required by the mandatory Chapter 04 labs. Commercial Server/Data Center, Kubernetes, managed databases, paid CI, and enterprise identity are optional/later-course boundaries.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.