A rolling update in twenty lines of shell

Compose has no rolling update, and the thing it does not have is twenty lines of shell for a two-replica service.

for i in 1 2; do
  docker compose up -d --no-deps --scale "app=$((i+1))" app

  ./bin/wait-healthy "app-$((i+1))" 30 || { rollback; exit 1; }

  docker compose stop "app-$i" && docker compose rm -f "app-$i"
done

docker compose up -d --scale app=2 app

Scaling up, waiting for health, then removing the old container is the whole algorithm, and the part that is easy to get wrong is the health wait — a container that is running is not a container that is serving. Twenty lines against an orchestrator is not an argument that orchestrators are unnecessary; it is an argument about what two replicas on one host actually need.