- Status: done
- Date: 2026-06-12
- Specs touched:
APP_LIFECYCLE.md(# Locked: override file contents — newcontainer_namebullet)
Closes #83 and the CONTAINER_NAME replica-suffix gap recorded in per-app-logs.md # Known gaps & deviations. The brain computed malmo-<id>-<service> (MainContainerName) and handed it to host-agent's journalctl CONTAINER_NAME=<name> follow, but that string was only a Docker network alias — compose named the running container malmo-<id>-<service>-1, and Docker's journald driver tags lines with the real, replica-suffixed name. Caddy kept working (it resolves the alias via Docker DNS), the dev inner loop kept working (the fake host-agent's ticker doesn't consult names), and the Logs tab was silently a no-op on real hardware.
writeOverridepinscontainer_nameon the main service (internal/lifecycle/lifecycle.go):container_name: malmo-<id>-<main_service>in the main service's override entry, the same stem as the ingress alias and Caddy upstream. The running container's name now is the stringMainContainerNamereturns, so journald'sCONTAINER_NAME=tag matches exactly. Sidecars stay unpinned — an explicitcontainer_nameforbidscompose scale, and only the main service is single-replica by design (the pin makes that assumption structural rather than conventional). Same pattern as the managed services' fixedcontainer_nameexec handle (internal/lifecycle/services.go).- No journalsource change, no wire-contract change —
BRAIN_HOST_PROTOCOL.md#journal_followandLOGGING.md# Per-app logs already described the exact-match contract as if it held; the pin makes them true. The two "Known gap" comments (MainContainerName's doc, thejournalsourcepackage doc) are replaced with one line each stating the pin. APP_LIFECYCLE.md# Locked: override file contents gains thecontainer_namebullet (the section enumerates exactly what the override stamps, so the pin had to land there in the same change).
Rejected alternatives are recorded on the issue: hardcoding the -1 suffix in MainContainerName (fragile, silently drops logs from any scaled service) and a journald tag log-opt + CONTAINER_TAG= match (forces driver: journald into every override and redefines the name as a tag; journalctl has no field glob, so a pure journalsource-side fix was never viable).
- New
TestOverridePinsMainContainerName(internal/lifecycle/lifecycle_test.go), on the existing multi-servicemigrateJobComposefixture: the main service's override entry carriescontainer_name: malmo-<id>-web, both sidecars (migrate,seed) carry none, andMainContainerNamereturns the identical string — the pinned name and the queried name can't drift apart. make checkgreen (gofmt, vet, OpenAPI freshness, full Go suite).
- Issue text vs. doc discipline: #83 says to "update
docs/progress/per-app-logs.md# Known gaps" — progress entries are frozen snapshots, so that entry is untouched and this one closes the gap by reference instead. - Pre-existing containers keep their suffixed name until recreated. The pin lands in the override at install/update time; an instance installed before this change keeps
…-1(and a broken Logs tail) until its next override regeneration recreates the container. Acceptable: no real-hardware deployments predate this fix. - The outer-loop proof (journald lines actually matching on a booted Debian target) remains with the per-app-logs real-hardware verification — this was the first blocker for that, not the whole of it.
- Real-host verification of the full Logs path (
journalctl CONTAINER_NAME=malmo-<id>-<service>returning live lines) when the outer-loop QEMU flow runs next. - The remaining
per-app-logs.mdgaps (member-visible household logs, empty-state hint, Logs surface moving to an app detail card) are unchanged.