Run compose like the terminal does, and follow changes made there (0.67.2)
CI / check (push) Successful in 4m48s
CI / build-and-push (push) Failing after 16m2s

StackPilot called `docker compose -p <folder> -f compose.yaml`, which is
not what `docker compose` typed in the stack's folder does. A
compose.override.yaml or a COMPOSE_FILE in .env applied in a terminal
and was dropped by the next Start in StackPilot, which recreated the
containers without it. A compose file's `name:` or a COMPOSE_PROJECT_NAME
in .env made the terminal's containers invisible: the stack showed as
stopped while running, and Start set up a second copy next to it.

Compose now runs in the stack's folder without -f, so it loads the same
files, and -p is the name compose itself picks there: COMPOSE_PROJECT_NAME
from .env, else the last `name:` among the compose files (${VAR} from
.env), else the folder name. A stack StackPilot already runs under its
folder name keeps that name while it has containers or volumes under it
and none under the declared one: switching would have brought it up
again on empty volumes. Backups record the project, and a restore takes
it from the files it has just put back.

Changes made outside StackPilot now reach every view:

- docker_watch_service reads Docker's events once for the backend. The
  dashboard rollup (cached 25s) and the stats cache are stale as soon as
  the event generation moves; the tiles followed a terminal `compose
  stop` after 15-30s, now within a second.
- Image events re-read the local digests, so a `docker compose pull` in
  a terminal clears the update badge instead of leaving it until the
  next registry check (an hour by default).
- The browser catches up when its event socket reconnects; what changed
  while it was down waited for the 60s poll.
- The stack detail carries a revision of the compose file and .env;
  saving after they changed on disk is refused with 409 and the editor
  offers to overwrite or to load the version on disk. A save used to
  replace a terminal edit without a word.
- A stack whose folder is gone is flagged "folder missing" instead of
  passing for an ordinary stopped stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fx7gcuy8d6nPd95g43rGHu
This commit is contained in:
menzeljandClaude Opus 5.5 committed 2026-10-15 15:14:40 +02:00
1 parent d6cebb3371
commit 06234c1067
21 files changed
+811 -45

No files matched your search

+21 -1
View File
@@ -151,6 +151,10 @@ def _stack_summary(
"status": status,
"service_count": total,
"running_count": running,
# The folder is gone (removed in a terminal, a share not mounted): the
# row stays — its git source, schedules and settings are still here —
# but says so instead of passing for an ordinary stopped stack.
"missing": not os.path.isdir(compose_service.stack_dir(stack.id)),
"created_at": stack.created_at,
"updated_at": stack.updated_at,
}
@@ -290,6 +294,7 @@ def get_stack(
# What is holding the stack, so the page can offer to stop it instead
# of only showing a spinner that never ends.
"lock": _lock_view(lock, stack_id),
"missing": not os.path.isdir(compose_service.stack_dir(stack_id)),
# Inline credentials masked for the read-only role, like the .env.
"yaml": (
compose_service.read_compose(stack_id)
@@ -300,6 +305,9 @@ def get_stack(
# from the read-only role — same reasoning as the admin-only file
# browser. Non-admins still get status, services and the compose file.
"env": compose_service.read_env(stack_id) if user.role == "admin" else "",
# For the editor's save: see StackUpdate.base_revision. Admins only —
# it is a hash over the .env, which this role is not shown.
"revision": compose_service.stack_revision(stack_id) if user.role == "admin" else None,
"containers": containers,
"created_at": stack.created_at,
"updated_at": stack.updated_at,
@@ -315,6 +323,18 @@ def update_stack(
user: User = Depends(require_admin),
) -> dict:
stack = _get_stack_or_404(session, stack_id)
if body.base_revision and (body.yaml is not None or body.env is not None):
current = compose_service.stack_revision(stack_id)
if current != body.base_revision:
raise HTTPException(
status_code=409,
detail={
"error": "changed_on_disk",
"detail": f"The compose file or .env of '{stack_id}' was changed outside "
"StackPilot after it was opened here",
"revision": current,
},
)
if body.yaml is not None:
compose_service.write_compose(stack_id, body.yaml)
if body.env is not None:
@@ -341,7 +361,7 @@ def update_stack(
session, user=user.username, action="stack.update", target=stack_id,
ip=_client_ip(request),
)
return _stack_summary(stack)
return {**_stack_summary(stack), "revision": compose_service.stack_revision(stack_id)}
@router.delete("/{stack_id}")