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.
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.
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.
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.
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?
The database is the primary durable application store.
sonarqube_data carries SonarQube search/index data
and is not a replacement for database backup/recovery.
Why is
jdbc:postgresql://localhost:5432/sonar normally
wrong when PostgreSQL is another container?
Because localhost inside SonarQube refers to the SonarQube
container itself. A sibling DB on the same Docker network is
reached by its service/container DNS name such as
db.
What does an image digest add beyond an exact version tag?
It identifies resolved image content. Recording both makes the human release selection and the pulled artifact independently auditable.
Does running SonarQube in Docker remove Linux
vm.max_map_count requirements?
No. Embedded Elasticsearch still depends on the Linux kernel underneath the container runtime.
Why should temp be writable but not treated as durable business state?
It is runtime scratch needed by SonarQube/Elasticsearch. Durable project and configuration history belongs in the supported database; temp content should not drive recovery.
Official references and version notes
- SonarQube Community Build downloads — current Community Build release identity.
- Official SonarQube Docker image tags — current official image tags and digests.
- Prepare the Docker installation — pre-installation checks and named-volume guidance.
- Set up and start your container — docker run/Compose, JDBC environment variables, ports, and volume warnings.
- Linux pre-installation — embedded-search host limits and writable /tmp requirements.
- Configuration methods — preferred Docker environment-variable configuration model.
- System properties — current JDBC and web-server property names.
- Installing database — supported database versions and H2 non-production boundary.
- Updating Community Build — container recreation and persistent-state upgrade guidance.
- SonarQube official image overview — official image usage, host prerequisites, port and edition tags.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.