Docker, Containers, Persistent Storage, and Production Deployment: Configuration, Design Patterns, and Trade-Offs
Choose container patterns deliberately by comparing Docker run and Compose, named volumes and bind mounts, runtime configuration and image baking, tags and digests, and lab versus production topology.
Learning objectives
- Compare ad-hoc docker run commands with a version-controlled Compose deployment contract.
- Explain why current SonarSource guidance prefers named volumes over bind mounts for key SonarQube paths.
- Separate runtime system properties and secret delivery from immutable image construction.
- Choose between version tags and digest pinning based on change-control and portability needs.
- Design a production-shaped topology with external database, private backend network, loopback/internal application exposure, reverse proxy/TLS, resource limits, and rollback evidence.
1. Production container design starts with state and trust
The fastest command is not automatically the safest operating model. Ask first: which state must survive; who can read secrets; which host/network boundary is trusted; how do we identify image content; how are logs retained; how does an operator roll forward or recover after a failed change?
2. docker run versus Docker Compose
| Dimension | docker run |
Compose |
|---|---|---|
| Best fit | Small lab, diagnosis, one-off reproduction | Repeatable multi-container local/self-managed stack |
| Reviewability | Command history/script required | Declarative YAML can be reviewed/versioned |
| Networks/volumes | Explicit CLI creation/order | Declared together with services |
| Secrets risk | Literal CLI values leak into history | Literal YAML values leak into source control; use external/runtime injection |
| Failure isolation | Easy to vary one command during diagnosis | Configuration drift is easier to detect |
3. A production-shaped local Compose contract
This example remains a lab: one SonarQube container and one PostgreSQL container. It deliberately keeps database credentials outside the committed YAML via variable interpolation. A real production secret manager is a separate boundary.
name: sq-container-lab
services:
db:
image: postgres:17.11
environment:
POSTGRES_DB: sonar
POSTGRES_USER: sonar
POSTGRES_PASSWORD: ${SQ_DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
networks: [backend]
sonarqube:
image: sonarqube:26.9.0.129388-community
depends_on: [db]
environment:
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: ${SQ_DB_PASSWORD}
ports:
- "127.0.0.1:9000:9000"
volumes:
- data:/opt/sonarqube/data
- extensions:/opt/sonarqube/extensions
- logs:/opt/sonarqube/logs
networks: [backend]
networks:
backend:
internal: false
volumes:
pgdata:
data:
extensions:
logs:
Use a local untracked environment file containing only fake lab credentials if you need interpolation. For production, integrate a supported orchestrator/secret store and restrict Docker daemon access. Never commit real DB passwords.
4. Named volumes versus bind mounts
Current SonarSource Docker preparation guidance specifically
instructs operators to create Docker volumes for data,
logs, and extensions and warns that bind
mounts can prevent plugins from populating correctly. That is
stronger than a generic “either works” Docker tutorial.
| Choice | Strength | Risk |
|---|---|---|
| Named volume | Docker-managed ownership/path semantics; matches SonarSource guidance | Less directly browsable on host; still needs backup/retention design |
| Bind mount | Direct host-path control | UID/permission/SELinux/path semantics can break startup/plugins; not the default recommended pattern |
If an organization must use bind mounts, treat that as a deliberate compatibility/security design requiring current documentation and tested ownership/SELinux behavior—not a casual substitution.
5. Runtime secret injection versus baked configuration
A password in a Dockerfile becomes image history and can leak to
registries/caches. A password committed in Compose becomes
repository history. A literal -e PASSWORD=... command
becomes shell history. The safer pattern is to keep immutable images
free of environment-specific secrets and inject credentials at
runtime from a controlled secret source.
6. Pinned version tag versus digest pinning
sonarqube:26.9.0.129388-community is readable and
release-specific. A digest such as
sha256:62930c7f510534bb2bf551ca69ad3ee6f8e12b4116d394597c64cefccbb3313b
identifies resolved content more strictly. Many teams record both:
the tag communicates intent and the digest proves artifact identity.
# Human-readable release selection
image: sonarqube:26.9.0.129388-community
# Stronger immutability after validation
# image: sonarqube@sha256:62930c7f510534bb2bf551ca69ad3ee6f8e12b4116d394597c64cefccbb3313b
Do not copy this authoring-time digest forever. Re-resolve and review the digest when deliberately moving to a new release.
7. Container user and filesystem ownership
The official current image runs as non-root
sonarqube (UID 1000 in the published build). Named
volumes usually reduce host-path friction. Bind-mounted paths must
be writable by the effective container user and compatible with host
security policy. Changing the container to root to “make permissions
work” removes a safety boundary instead of fixing ownership.
8. Read-only root filesystem: advanced hardening needs writable scratch
Current Linux guidance shows a read-only-container pattern only when
required writable paths are provided, including a writable
/tmp because SonarQube now includes Elasticsearch 8.x.
Treat read-only root filesystem design as an advanced hardening
exercise: validate all required writable mounts/tmpfs paths and do
not blindly add read_only: true.
9. Single-container lab versus production topology
flowchart TD U[Users / CI scanners] --> RP[Reverse proxy / TLS controlled ingress] RP --> SQ[SonarQube container private/internal port] SQ --> DB[(Supported external DB separate backup/HA)] SQ --> V[(Named volumes search/extensions/logs)] OBS[Logs / metrics / alerts] --> SQ SEC[Secret source] --> SQ HOST[Hardened host limits + capacity] --> SQ
Containerization does not make a single Community Build container highly available. Data Center Edition clustering is a separate commercial architecture and is not simulated by simply starting two identical Community Build containers against one database.
10. Decision table
| Requirement | Recommended direction | Evidence |
|---|---|---|
| One disposable workstation lab | Pinned tag + run/Compose + named volumes | Manifest, digests, mounts, logs |
| Repeatable self-managed single node | Compose/orchestration + external DB + reverse proxy/TLS + secret manager | Reviewed config + DB backup + health + rollback plan |
| Strict artifact immutability | Record tag and deploy approved digest | Registry/inspect digest |
| Need clustered app/search nodes | Evaluate current Data Center Edition architecture | Edition/license and current cluster docs |
| Need managed SaaS rather than self-hosting | Evaluate SonarQube Cloud as a distinct product | Cloud product requirements; not Docker configuration |
Knowledge check
Why does Compose not automatically solve secret management?
Because values can still be committed to YAML or exposed to Docker administrators. Compose improves deployment declaration, not the trust model of credentials.
Why does SonarSource prefer named volumes for key Docker paths?
Named volumes avoid many host-path ownership/permission/plugin-population problems and match the supported installation guidance.
When is digest pinning useful?
When deployment requires strict artifact identity. The release tag remains useful for human intent, while the digest proves the content selected.
Should you run the SonarQube container as root to fix a bind-mount permission error?
No. Diagnose ownership/security-context compatibility and use supported writable volumes. Root removes a least-privilege boundary.
Can two Community Build containers pointed at one DB be called Data Center Edition?
No. Data Center Edition is a distinct commercial clustered architecture with specific app/search-node topology and requirements.
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.