Checkpoint Lab — Docker, Containers, Persistent Storage, and Production Deployment
Operate a disposable containerized SonarQube stack, prove persistence across container recreation, diagnose a reversible network mistake, and produce an edition/version-aware deployment dossier with guarded teardown.
Learning objectives
- Build an edition/version-aware container deployment dossier before running the lab.
- Predict image, network, volume, database, log, and health state changes and verify them independently.
- Prove that DB-backed SonarQube state survives replacement of the SonarQube container.
- Inject and diagnose a wrong database-host network configuration while preserving first-failure evidence.
- Perform a guarded teardown and clearly separate lab persistence proof from real backup, upgrade, HA, and disaster-recovery requirements.
1. Checkpoint charter and assumptions
Build exactly one local lab stack using
sonarqube:26.9.0.129388-community and
postgres:17.11. Record the SonarQube image digest you
actually pull, Docker Engine/Desktop version, host OS/runtime model,
CPU/RAM allocation, database image digest, network name, volume
names, and loopback port.
2. Write predictions before mutation
At minimum predict and later verify these state changes:
-
After creating
sq_netand named volumes, Docker metadata changes but no SonarQube application state exists yet. -
After PostgreSQL starts, the
sq_pgdatavolume becomes the durable database store. -
After SonarQube starts, data/extensions/log volumes become
attached and the application reaches
UP. -
After a manual project is created, that project should survive
removal/recreation of
sq-labbecause PostgreSQL remains attached. -
After replacing the JDBC host with
localhost, the new SonarQube container should fail to reach the sibling database even though PostgreSQL remains healthy.
3. Preflight and evidence directory
mkdir -p sq-ch04-evidence
docker version > sq-ch04-evidence/docker-version.txt
docker info > sq-ch04-evidence/docker-info.txt
docker ps -a --filter name=sq-lab --filter name=sq-db
docker volume ls --filter name='sq_'
docker network ls --filter name='^sq_net$'
On Linux also record vm.max_map_count,
fs.file-max, ulimit -n, and
ulimit -u. On Docker Desktop, record the Docker
Desktop/runtime resource allocation instead of pretending the
Windows/macOS host directly owns Linux container sysctls.
4. Build the clean stack
docker pull sonarqube:26.9.0.129388-community
docker pull postgres:17.11
docker image inspect sonarqube:26.9.0.129388-community --format '{{json .RepoDigests}}' > sq-ch04-evidence/sonarqube-repodigests.json
docker image inspect postgres:17.11 --format '{{json .RepoDigests}}' > sq-ch04-evidence/postgres-repodigests.json
docker network create sq_net
docker volume create sq_data
docker volume create sq_extensions
docker volume create sq_logs
docker volume create sq_pgdata
docker run -d --name sq-db --network sq_net --network-alias db \
-e POSTGRES_DB=sonar -e POSTGRES_USER=sonar \
-e POSTGRES_PASSWORD=FAKE_LOCAL_SQ_DB_ONLY \
-v sq_pgdata:/var/lib/postgresql/data postgres:17.11
docker exec sq-db pg_isready -U sonar -d sonar
docker run -d --name sq-lab --network sq_net \
-p 127.0.0.1:9000:9000 \
-e SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar \
-e SONAR_JDBC_USERNAME=sonar \
-e SONAR_JDBC_PASSWORD=FAKE_LOCAL_SQ_DB_ONLY \
-v sq_data:/opt/sonarqube/data \
-v sq_extensions:/opt/sonarqube/extensions \
-v sq_logs:/opt/sonarqube/logs sonarqube:26.9.0.129388-community
5. Verify clean health and record topology
docker inspect sq-lab > sq-ch04-evidence/sq-lab-inspect-initial.json
docker inspect sq-db > sq-ch04-evidence/sq-db-inspect-initial.json
docker network inspect sq_net > sq-ch04-evidence/sq-net-initial.json
docker logs sq-lab > sq-ch04-evidence/sq-lab-initial.log 2>&1
docker logs sq-db > sq-ch04-evidence/sq-db-initial.log 2>&1
curl -sS http://127.0.0.1:9000/api/system/status | tee sq-ch04-evidence/system-status-initial.json
Wait for UP. Then complete bootstrap login locally,
change the default administrator password, and create the manual
project sq-container-persistence-lab. Do not save the
new password in the evidence packet.
6. Persistence proof
docker stop sq-lab
docker rm sq-lab
docker volume inspect sq_data sq_extensions sq_logs sq_pgdata > sq-ch04-evidence/volumes-before-recreate.json
docker exec sq-db pg_isready -U sonar -d sonar
docker run -d --name sq-lab --network sq_net \
-p 127.0.0.1:9000:9000 \
-e SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar \
-e SONAR_JDBC_USERNAME=sonar \
-e SONAR_JDBC_PASSWORD=FAKE_LOCAL_SQ_DB_ONLY \
-v sq_data:/opt/sonarqube/data \
-v sq_extensions:/opt/sonarqube/extensions \
-v sq_logs:/opt/sonarqube/logs sonarqube:26.9.0.129388-community
Verify UP, sign in with the changed password, and
confirm the project remains. Record the result as
container-replacement persistence evidence, not as
disaster-recovery evidence.
7. Inject one reversible network mistake
Preserve the healthy evidence, then replace only the SonarQube container with the intentionally wrong JDBC hostname:
docker stop sq-lab && docker rm sq-lab
docker run -d --name sq-lab --network sq_net \
-p 127.0.0.1:9000:9000 \
-e SONAR_JDBC_URL=jdbc:postgresql://localhost:5432/sonar \
-e SONAR_JDBC_USERNAME=sonar \
-e SONAR_JDBC_PASSWORD=FAKE_LOCAL_SQ_DB_ONLY \
-v sq_data:/opt/sonarqube/data \
-v sq_extensions:/opt/sonarqube/extensions \
-v sq_logs:/opt/sonarqube/logs sonarqube:26.9.0.129388-community
Expected result: PostgreSQL remains healthy, but SonarQube fails to
connect because localhost resolves inside
sq-lab. Do not delete volumes or restart PostgreSQL
while diagnosing.
8. Diagnose from independent evidence
docker exec sq-db pg_isready -U sonar -d sonar
docker network inspect sq_net > sq-ch04-evidence/sq-net-broken.json
docker inspect sq-lab > sq-ch04-evidence/sq-lab-broken.json
docker logs sq-lab > sq-ch04-evidence/sq-lab-broken.log 2>&1
Your written diagnosis should say: database readiness is good; both containers are on the intended network; JDBC configuration points to the wrong endpoint; the failed layer is application-to-database addressing, not database durability, search storage, quality policy, or SonarQube edition.
9. Repair the smallest layer and re-verify
docker stop sq-lab && docker rm sq-lab
docker run -d --name sq-lab --network sq_net \
-p 127.0.0.1:9000:9000 \
-e SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar \
-e SONAR_JDBC_USERNAME=sonar \
-e SONAR_JDBC_PASSWORD=FAKE_LOCAL_SQ_DB_ONLY \
-v sq_data:/opt/sonarqube/data \
-v sq_extensions:/opt/sonarqube/extensions \
-v sq_logs:/opt/sonarqube/logs sonarqube:26.9.0.129388-community
Record final /api/system/status, logs, mounts, network
membership, and project persistence. The repaired run should differ
from the broken run only in the causal JDBC host configuration.
10. Required evidence packet
- SonarQube and PostgreSQL exact tags plus observed digests;
- Docker/runtime version and resource allocation;
- Linux host-limit evidence where applicable;
- network and volume manifests before/after recreation;
- sanitized container inspect output;
- healthy startup/status evidence;
- proof that the manual project survives SonarQube container replacement;
- broken-run first-failure logs and written causal diagnosis;
- repaired-run status;
- assumptions/limitations note explicitly saying this is not backup/restore, HA, TLS, or upgrade validation.
11. Production delta
A production handoff must add supported database backup/recovery, reverse proxy/TLS, secret manager, durable log/monitoring pipeline, capacity sizing, alerting, image/provenance policy, maintenance/upgrade path, plugin compatibility, vulnerability response, least-privilege Docker/orchestrator access, and tested recovery objectives. Data Center Edition and Kubernetes/OpenShift are separate architectures covered later.
12. Guarded teardown and final verification
docker ps -a --filter name=sq-lab --filter name=sq-db
docker volume ls --filter name='sq_'
docker network inspect sq_net --format '{{json .Containers}}'
# Manually verify names and that no retained lab work is needed.
docker stop sq-lab sq-db
docker rm sq-lab sq-db
docker network rm sq_net
docker volume rm sq_data sq_extensions sq_logs sq_pgdata
# Verify only expected lab resources were removed
docker ps -a --filter name=sq-lab --filter name=sq-db
docker volume ls --filter name='sq_'
docker network ls --filter name='^sq_net$'
Retain the evidence packet outside the removed volumes. Never generalize this teardown into a prune command.
Knowledge check
Which prediction demonstrates that you understand container replacement versus durable state?
That deleting/recreating only sq-lab should not
delete DB-backed project/configuration state when PostgreSQL and
its volume remain intact.
During the intentionally broken run, what evidence proves PostgreSQL is not the failed layer?
pg_isready succeeds and DB logs show healthy
readiness while SonarQube logs show connection attempts against
its own localhost endpoint.
Why is the project-survival check not a backup test?
No independent backup was created or restored. The exercise only reuses the same live PostgreSQL volume and demonstrates container replacement persistence.
What must be redacted before sharing container inspect output?
Passwords/tokens, private endpoints, sensitive labels/environment values, and other credentials. Preserve raw evidence securely but share a sanitized minimum.
What is the safe teardown principle?
Enumerate and manually verify exact lab resource names first, then remove only those objects—never use broad prune or volume-removal shortcuts.
What chapter comes next and why?
Chapter 05 moves from single-host containers to Kubernetes and OpenShift deployment patterns, where persistent volumes, secrets, scheduling, probes, and orchestration become explicit cluster-level concerns.
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.