8.3 Local Deployment and Rollback Experiment
Prerequisites: Pages 1 and 2 of this chapter Estimated time: 30 minutes Practice environment: Continue using
$release_lab_dir; the container section uses Docker Engine 28+ with default bridge/NAT networking, Bash, and curl 7.71+ You are ready to continue when: you can build two identifiable versions from one JAR, roll back using a known-good image ID, and verify the result
The automated workflow has been reviewed, but remote registries and production environments are outside this exercise's scope. Stay on the local machine and use the same JAR for version switching, health checks, evidence recording, an actual rollback, and cleanup.
cd "$release_lab_dir"Warm-up: Validate the Build Artifact
mvn --batch-mode verify
sha256sum target/release-lab-1.0.0.jar
jar tf target/release-lab-1.0.0.jar | headAvailable on macOS via shasum -a 256. The temporary project has no Git history by default. Record source-file and JAR hashes; record a commit SHA only when the source actually came from a commit. Keep the build log and do not put -DskipTests into the default release path.
After launching the JAR, request /health and / from a separate terminal, then terminate the recently started PID using a standard termination signal. Avoid using kill -9 as the preferred method, as it does not give the application a chance to clean up.
Challenge: Container Candidate Version
First verify known-good v1, run v2, then deliberately rehearse a rollback by starting the recorded v1 image ID. Check /health, /version, and the container's actual image again. Both images reuse the same JAR and differ only in a public version marker. This verifies version switching, not business compatibility or zero downtime. The temporary port changes between containers, so it does not model a production traffic entry point.
(
set -e
lab_tag="workshop-ch08-release:${RANDOM}-${RANDOM}"
good_tag="${lab_tag}-good"
candidate_tag="${lab_tag}-candidate"
active_id=
cleanup_release() {
if [ -n "$active_id" ]; then
docker stop "$active_id" >/dev/null 2>&1 || true
fi
docker image rm "$candidate_tag" "$good_tag" >/dev/null 2>&1 || true
}
trap cleanup_release EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
start_and_check() {
active_id=$(docker run --detach --rm --env HOST=0.0.0.0 \
--publish 127.0.0.1::8080 "$1")
published=$(docker port "$active_id" 8080/tcp)
host_port=${published##*:}
curl --fail --silent --show-error --retry 10 --retry-all-errors \
--retry-delay 1 --max-time 2 "http://127.0.0.1:$host_port/health"
observed=$(curl --fail --silent --show-error --max-time 2 \
"http://127.0.0.1:$host_port/version")
test "$observed" = "$2"
printf 'version=%s image=%s\n' "$observed" "$1"
}
docker build --build-arg APP_VERSION=v1 -t "$good_tag" .
good_image=$(docker image inspect "$good_tag" --format '{{.Id}}')
start_and_check "$good_image" v1
docker stop "$active_id"
active_id=
docker build --build-arg APP_VERSION=v2 -t "$candidate_tag" .
candidate_image=$(docker image inspect "$candidate_tag" --format '{{.Id}}')
test "$good_image" != "$candidate_image"
start_and_check "$candidate_image" v2
docker logs "$active_id"
docker image inspect "$candidate_tag" --format '{{json .RepoDigests}}'
docker stop "$active_id"
active_id=
start_and_check "$good_image" v1
test "$(docker inspect "$active_id" --format '{{.Image}}')" = "$good_image"
printf 'rollback verified\n'
)Expect version=v1, version=v2, version=v1, then rollback verified. The subshell removes this run's container and image tags on exit. Local images usually have no registry RepoDigests, but do have content-addressed image IDs. Record a registry digest after pushing; this lab performs no push.
Release and Rollback Criteria
Candidate commit / artifact hash / image digest:
Tests passed prior to release:
Database migration backward compatibility:
Health check path and timeout:
Observation window:
Stop promotion condition:
Known-good digest:
Rollback command and owner:
Post-rollback validation:Rollback is not "just pull latest." It should restore traffic or deployment declarations to a known-good, immutable version. If a database migration is not backward compatible, simply reverting the image may still result in failure, forward compatibility or recovery plans must be designed and pre-validated upfront.
Troubleshooting
| Phenomenon | Action |
|---|---|
| Port conflict | Check the listener; prefer switching to a random loopback port, do not kill unknown processes |
| Still running old code in production | Compare deployment digest, container .Image, response version field, and cache layer |
no main manifest attribute | Verify JAR manifest and packaging plugin; do not assume standard JARs are executable |
| Health returns 200 but business logic fails | Health check is too shallow; add critical dependency readiness checks and external synthetic requests |
| Container exits immediately upon startup | Check startup logs, exit code, entry command, configuration, and permissions docker logs |
After the experiment, confirm the temporary project is safe before deletion:
cd
ls -ld -- "$workdir"
rm -r -- "$workdir"
unset release_lab_dirOnce this local exercise passes, you have verifiable version-switching and rollback records. Volume One continues with program syntax and structure.