Skip to content

VOL 00 / CH 08 / LESSON 03

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.

bash
cd "$release_lab_dir"

Warm-up: Validate the Build Artifact ​

bash
mvn --batch-mode verify
sha256sum target/release-lab-1.0.0.jar
jar tf target/release-lab-1.0.0.jar | head

Available 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.

bash
(
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 ​

text
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 ​

PhenomenonAction
Port conflictCheck the listener; prefer switching to a random loopback port, do not kill unknown processes
Still running old code in productionCompare deployment digest, container .Image, response version field, and cache layer
no main manifest attributeVerify JAR manifest and packaging plugin; do not assume standard JARs are executable
Health returns 200 but business logic failsHealth check is too shallow; add critical dependency readiness checks and external synthetic requests
Container exits immediately upon startupCheck startup logs, exit code, entry command, configuration, and permissions docker logs

After the experiment, confirm the temporary project is safe before deletion:

bash
cd
ls -ld -- "$workdir"
rm -r -- "$workdir"
unset release_lab_dir

Once this local exercise passes, you have verifiable version-switching and rollback records. Volume One continues with program syntax and structure.

→ Proceed to Volume One: the programming-basics section

Built with VitePress | Software Systems Atlas