Clicking "Update" on a stack member (and the native auto-update daemon
updating one) redeployed the whole compose stack instead of updating just
that container. Match Watchtower behaviour: always recreate the single
container with a re-pull. The recreate endpoint preserves config + compose
labels, so the container stays part of its project.
Collapse all update surfaces to a single-container recreate and drop the
now-dead stack-aware routing:
- frontend: "Update now" button, list badge and bulk "Update selected" now
recreate each container individually; remove standalone/stack/external
routing, the external refusal, the PortainerStackUpdate gate and the
stack-update confirm dialog.
- daemon: route every outdated candidate through updateStandalone; remove
updateStack, the stack/external grouping and the stackDeployer dependency.
- add a regression test asserting a Portainer-managed compose-stack member is
recreated individually, not stack-redeployed.
Behavioural notes: git/external compose containers are now auto-updated too
(were detect-only), and updating a stack member no longer requires
PortainerStackUpdate (same auth as the normal Recreate action).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- F1: test that clicking the badge/UpdateNowButton actually dispatches the update
(confirm->mutate) for standalone and stack, and not on dismiss.
- F2: Go test that a successful forced re-check repopulates the caches (a later
non-force read hits cache, no second registry HEAD).
- F3: throttle forced image-status re-checks against registry amplification —
coalesce concurrent forced re-checks of the same image via singleflight, plus a
5s per-image min-interval (== remoteDigestCache TTL) caching only successes. The
non-force path (daemon + background badges) is unchanged.
- F4: notifications are now per-container. Stack-member containers each emit their
own EventUpdated (not one aggregate stack event), Event carries the stack name
(from the com.docker.compose.project label), and the new image digest is fetched
best-effort by re-inspecting the container after the redeploy. Message:
'Environment | .. / Stack [<name>] / Update [<container>]: <old> -> <new>'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the container image-status badge actionable, matching native Portainer:
- Clicking "Update available" opens the update confirm dialog and runs the
existing update flow (standalone recreate-with-pull / stack redeploy), gated
and disabled while in flight to avoid a double submit. The confirm+apply logic
is extracted from UpdateNowButton into a shared useApplyContainerImageUpdate
hook so the details button and the list badge share one implementation.
- Clicking "Up to date" re-queries the registry. Because the server caches image
status (statusCache 5m + remoteDigestCache 5s), a plain refetch was a no-op, so
the endpoint gains an optional ?force=true that bypasses BOTH caches for a
manual re-check while still repopulating them; the default (auto badges + the
auto-update daemon) keeps using the caches unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1: single-container "Update now" and bulk "Update" now require
PortainerStackUpdate when the resolved path is a stack, disabling the
action with a tooltip / skipping it rather than letting the click 403.
F2: resolveContainerUpdatePath only matches a Docker Compose stack; a
same-named swarm/kubernetes stack is treated as external.
F3: SecondaryActions no longer renders an empty ButtonGroup when all of
recreate/duplicate/update-now are hidden.
F4: bulk update reports an explicit no-op toast and counts containers vs
stacks honestly in the success summary.
F5: bulk toasts use trimmed container names (no leading slash).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a discoverable per-container "Update now" action, shown only when the
image status is `outdated`, plus a bulk "Update selected" action in the
containers list.
Both manual paths share ONE apply primitive (applyContainerUpdate /
useUpdateContainerImage) that also backs the future M4 auto-update job:
- standalone container -> recreate-with-pull (existing recreate endpoint)
- stack-managed -> stack redeploy-with-pull (existing git/file stack
update mutations), so the container stays in its
stack and is never recreated out-of-band
- externally-managed -> refused; the details button is disabled with an
compose explanatory tooltip and the bulk action skips it
Decision logic lives in the pure, unit-tested resolveContainerUpdatePath /
groupContainersForUpdate helpers. The bulk action filters to outdated
containers and redeploys each owning stack exactly once even when several of
its containers are selected, reporting per-item success/failure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>