Chapter 04Lesson 03~125 minutes

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.

ComposeNamed volumesSecretsDigest pinningProduction 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.

Environment variables are not a magic secret vault. Docker administrators can inspect container configuration. Protect the Docker daemon, use least privilege, and choose an organizational secret-delivery mechanism that is compatible with the current SonarQube property model.

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

Production-shaped single-node container 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?

Why does SonarSource prefer named volumes for key Docker paths?

When is digest pinning useful?

Should you run the SonarQube container as root to fix a bind-mount permission error?

Can two Community Build containers pointed at one DB be called Data Center Edition?

Next lesson

From design choices to causal troubleshooting

Lesson 4 breaks the stack intentionally and teaches an evidence-first diagnostic sequence for DNS, database, volumes, host limits, secrets, image drift, and destructive redeploy mistakes.

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.