← Start of tutorial

WIUNIX ACADEMY

Docker Compose blue/green: health gates, proxy switch and rollback

Page 2 of 3. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Red Hat Enterprise Linux 9Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment5 editable values
04

LET’S BUILD

Pin the runtime, create green’s network and start blue

The recorded multi-platform manifest-index digest was reviewed from the Docker Official Image metadata on 2026-11-05: stable Nginx 1.30.6 on Alpine. The stable-alpine tag is descriptive; the digest fixes the selected content even after that tag moves. Pulling and inspecting the digest is a reader action, not a claim that the author ran the image on every OS/architecture.

Start with the blue route hard-linked as active.conf. The complete proxy directory is mounted, so later rename changes are visible; a single-file bind mount could retain an old inode. Validate both Compose documents, prepare green’s inactive container/network, then start blue with its gateway. --wait only evaluates configured health/running state, so verify the actual routed release afterward.

bash

Disposable Linux lab VM; same authorized Bash session

sudo ln -- '/srv/academy-bluegreen-demo/proxy/versions/blue.route' '/srv/academy-bluegreen-demo/proxy/active.conf' || exit 1
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' config --quiet || exit 1
docker_lab compose -p 'academybg-green' -f '/srv/academy-bluegreen-demo/green.yml' config --quiet || exit 1
docker_lab pull 'nginx:stable-alpine@sha256:0e52bd054126cdc040b1276c0af347aeb02a41b9cfe305d6094767ac30e97949' || exit 1
docker_lab image inspect 'nginx:stable-alpine@sha256:0e52bd054126cdc040b1276c0af347aeb02a41b9cfe305d6094767ac30e97949' --format '{{json .RepoDigests}}' || exit 1
docker_lab compose -p 'academybg-green' -f '/srv/academy-bluegreen-demo/green.yml' create || exit 1
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' up -d --wait --wait-timeout 45 || exit 1
marker=$(curl --noproxy '*' --fail --silent --show-error --max-time 3 'http://127.0.0.1:18080/release') || exit 1
test "$marker" = BLUE || exit 1
printf 'Gateway release: %s\n' "$marker"
How do I know it worked?

Both documents validate, green remains not started, blue/gateway health gates pass and the gateway returns BLUE. An error leaves a partial lab to inspect.

05

LET’S BUILD

Start green and test readiness across the proxy’s network

Keep blue and the gateway running. Start only the green project, wait for its own healthcheck, then request the release marker directly on its loopback port and from the gateway container through green-app. The latter catches a missing external-network attachment or wrong alias that a local healthcheck cannot see.

These tiny checks establish only this demo’s HTTP readiness. A dependency becoming unhealthy later does not automatically switch Nginx back, and a healthcheck cannot prove migration compatibility, correct writes or safe retries. Do not route real traffic merely because Compose reports healthy.

bash

Disposable Linux lab VM; same authorized Bash session

docker_lab compose -p 'academybg-green' -f '/srv/academy-bluegreen-demo/green.yml' up -d --wait --wait-timeout 45 || exit 1
marker=$(curl --noproxy '*' --fail --silent --show-error --max-time 3 'http://127.0.0.1:18082/release') || exit 1
test "$marker" = GREEN || exit 1
marker=$(docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' exec -T gateway wget -T 3 -q -O - http://green-app/release) || exit 1
test "$marker" = GREEN || exit 1
How do I know it worked?

Green is healthy and returns GREEN both directly and from the existing gateway network; blue remains available for rollback.

06

LET’S BUILD

Select green on disk, test Nginx and request a reload

No concurrent operator edits this exclusive fixture. A new hard link and same-directory rename replace active.conf atomically on disk while preserving the already reviewed file’s SELinux label. Disk selection is not yet the running proxy state. Nginx -t checks the selected configuration before a reload is requested.

If validation fails, the panel restores blue on disk and exits without reloading. If the reload command fails after validation, keep both slots running and use the rollback section; disk and running state may differ. A successful signal is still not proof that every request already reaches green. The next section verifies fresh requests.

bash

Disposable Linux lab VM; same authorized Bash session

sudo ln -- '/srv/academy-bluegreen-demo/proxy/versions/green.route' '/srv/academy-bluegreen-demo/proxy/active.conf.next' || exit 1
sudo mv -T -- '/srv/academy-bluegreen-demo/proxy/active.conf.next' '/srv/academy-bluegreen-demo/proxy/active.conf' || exit 1
if ! docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' exec -T gateway nginx -t; then
  sudo ln -- '/srv/academy-bluegreen-demo/proxy/versions/blue.route' '/srv/academy-bluegreen-demo/proxy/active.conf.restore' || exit 1
  sudo mv -T -- '/srv/academy-bluegreen-demo/proxy/active.conf.restore' '/srv/academy-bluegreen-demo/proxy/active.conf' || exit 1
  docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' exec -T gateway nginx -t
  exit 1
fi
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' exec -T gateway nginx -s reload || exit 1
How do I know it worked?

The green selection validates and reload is requested, or blue is restored on disk after validation failure. Routed success is checked separately.

07

LET’S BUILD

Verify fresh routed requests and retain the old slot

Run five separate requests with Connection: close and require the exact GREEN marker. A failed or old-color response is not accepted as a successful switch. Inspect the proxy log and current selection; allow a bounded observation of the reload before repeating these same checks rather than destroying blue.

Fresh responses demonstrate only these five HTTP requests. They do not drain existing keep-alive/WebSocket streams or background work. Keep the old slot and immutable before-route until the application-specific rollback/drain window ends. This lab contains no long-lived work and deliberately supplies no generic production decommission command.

bash

Disposable Linux lab VM; same authorized Bash session

for sample in 1 2 3 4 5; do
  marker=$(curl --noproxy '*' --fail --silent --show-error --max-time 3 --header 'Connection: close' 'http://127.0.0.1:18080/release') || exit 1
  printf 'sample=%s release=%s\n' "$sample" "$marker"
  test "$marker" = GREEN || exit 1
  sleep 1
done
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' logs --tail 30 gateway
How do I know it worked?

Five fresh gateway requests return GREEN; both slots remain running and the rollback template remains available.

08

LET’S BUILD

Restore blue with its own health and routing checks

This is a stateless route rollback, not a data rollback. First confirm blue still serves BLUE directly. Select the preserved blue template, validate and reload the proxy, then require fresh gateway responses to return BLUE. If blue itself is unhealthy, stop and investigate; switching to a broken old slot is not recovery.

The hard-link selection refuses a leftover .next file instead of overwriting it. Inspect any such file from a failed attempt and reconcile exactly that path before retrying. Do not recreate the blue Compose project to reload only the gateway: an up operation could also replace containers or change network identity.

bash

Disposable Linux lab VM; same authorized Bash session

marker=$(curl --noproxy '*' --fail --silent --show-error --max-time 3 'http://127.0.0.1:18081/release') || exit 1
test "$marker" = BLUE || exit 1
sudo ln -- '/srv/academy-bluegreen-demo/proxy/versions/blue.route' '/srv/academy-bluegreen-demo/proxy/active.conf.next' || exit 1
sudo mv -T -- '/srv/academy-bluegreen-demo/proxy/active.conf.next' '/srv/academy-bluegreen-demo/proxy/active.conf' || exit 1
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' exec -T gateway nginx -t || exit 1
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' exec -T gateway nginx -s reload || exit 1
for sample in 1 2 3; do
  marker=$(curl --noproxy '*' --fail --silent --show-error --max-time 3 --header 'Connection: close' 'http://127.0.0.1:18080/release') || exit 1
  test "$marker" = BLUE || exit 1
  sleep 1
done
How do I know it worked?

The old slot is independently reachable and three fresh gateway requests return BLUE after the checked reload.

Follow the failed gate rather than forcing the switch

Use the matching read-only diagnostic. Keep both projects and their files while investigating. Do not disable SELinux/AppArmor, change Docker’s firewall backend, delete an existing listener or invoke a system-wide prune.

Compose is missing or an older launcher is selected

Check the selected package and plugin through the same local administrative helper. Do not fall back to a legacy docker-compose command, replace a running daemon or use a remote context to make the example pass.

bash
docker_lab version
docker_lab compose version
docker_lab compose up --help
The gateway cannot attach to green’s network

The green project create step must have succeeded before blue starts. Inspect that named project and network ownership; never create a guessed external network over someone else’s name or connect the proxy to the host network.

bash
docker_lab compose -p 'academybg-green' -f '/srv/academy-bluegreen-demo/green.yml' ps -a
docker_lab network inspect 'academybg-green_default'
Green stays unhealthy or direct requests fail

Inspect the candidate container, logs and host bind port. Health here depends on the marker file and its bind-mount access. On SELinux hosts, confirm the fresh directory’s label and relevant denial with the host owner; do not apply relabeling to an application directory.

bash
docker_lab compose -p 'academybg-green' -f '/srv/academy-bluegreen-demo/green.yml' ps -a
docker_lab compose -p 'academybg-green' -f '/srv/academy-bluegreen-demo/green.yml' logs --tail 40 green
sudo ls -ldZ '/srv/academy-bluegreen-demo/green'
ss -H -ltn '( sport = :18082 )'
Healthy green exists but gateway serves blue or 502

Read both disk selection and proxy diagnostics. A healthy local endpoint does not prove external-network reachability, DNS resolution or that a reload loaded the new route. Preserve blue and use the explicit rollback if the intended switch cannot be verified.

bash
sudo sha256sum '/srv/academy-bluegreen-demo/proxy/active.conf' '/srv/academy-bluegreen-demo/proxy/versions/blue.route' '/srv/academy-bluegreen-demo/proxy/versions/green.route'
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' exec -T gateway nginx -t
docker_lab compose -p 'academybg-blue' -f '/srv/academy-bluegreen-demo/blue.yml' logs --tail 40 gateway
Explore all tutorials ↗