# Pull the newest image for one compose stack and recreate it. # # The generic "update a stack that tracks a floating tag" playbook. Works for # any stack laid out per the house convention # (`/opt/docker/compose//compose.yaml`). # # Usage: # scripts/elway esh-docker-vm --playbook playbooks/update-stack-image.yaml \ # --var stack=drawio # # `service` and `container` default to `stack`. Override them when the # compose service name or `container_name:` differs from the directory name. # # ⚠ SCOPED TO ONE SERVICE ON PURPOSE. The recreate is `up -d `, never # a bare `up -d` — a bare one recreates every service in the project # (auto-memory `feedback_compose_up_recreates_whole_stack`). A stack that # really needs all its services bounced gets one run per service. # # ⚠ For stacks tracking a FLOATING tag (`:latest` and friends). A SHA-pinned # stack has nothing to pull; updating that means moving the pin, which is a # deliberate edit and not this. # # ⚠ Go template format strings are written bare (`{{.Image}}`). elway's # substitution only matches `{{ identifier }}`, so those pass through # untouched — but `{{end}}` / `{{else}}` DO match and would die as undefined # variables. Hence `{{json .State.Health}}` below instead of an if/else. vars: compose_root: /opt/docker/compose stack: "" service: "" container: "" steps: - name: Refuse to run without a stack name shell: test -n "{{ stack }}" changed_when: "false" - name: Record the image the service runs BEFORE the pull # Written to a file, not just echoed, so the verify phase can compare # against it — an "updated" claim nobody can falsify is not a result. shell: | ctr="{{ container }}"; [ -n "$ctr" ] || ctr="{{ service }}"; [ -n "$ctr" ] || ctr="{{ stack }}" docker inspect "$ctr" --format '{{.Image}}' | tee /tmp/elway-update-{{ stack }}.before changed_when: "false" - name: Pull the newest image for the service shell: | svc="{{ service }}"; [ -n "$svc" ] || svc="{{ stack }}" cd {{ compose_root }}/{{ stack }} docker compose pull "$svc" - name: Recreate the service on the pulled image shell: | svc="{{ service }}"; [ -n "$svc" ] || svc="{{ stack }}" cd {{ compose_root }}/{{ stack }} docker compose up -d "$svc" verify: - name: Service runs the image its tag now resolves to # `docker ps` saying "Up" only proves something is running. This asserts # the container's image id equals what the tag resolves to right now, and # reports whether that is actually a move off the pre-pull image. shell: | svc="{{ service }}"; [ -n "$svc" ] || svc="{{ stack }}" ctr="{{ container }}"; [ -n "$ctr" ] || ctr="$svc" cd {{ compose_root }}/{{ stack }} tag=$(docker compose config --images "$svc" | head -1) want=$(docker image inspect "$tag" --format '{{.Id}}') have=$(docker inspect "$ctr" --format '{{.Image}}') test "$want" = "$have" || { echo "MISMATCH: $ctr runs $have, $tag is $want"; exit 1; } before=$(cat /tmp/elway-update-{{ stack }}.before 2>/dev/null) if [ "$before" = "$have" ]; then echo "already current: $tag == $have (nothing newer upstream)" else echo "updated: $before -> $have ($tag)" fi changed_when: "false" - name: Container is up, and healthy if it declares a healthcheck shell: | ctr="{{ container }}"; [ -n "$ctr" ] || ctr="{{ service }}"; [ -n "$ctr" ] || ctr="{{ stack }}" docker inspect "$ctr" --format 'status={{.State.Status}} started={{.State.StartedAt}}' echo "health=$(docker inspect "$ctr" --format '{{json .State.Health}}' | head -c 120)" test "$(docker inspect "$ctr" --format '{{.State.Status}}')" = running changed_when: "false"