detached 86ac292

fix(deploy): cleanup failed containers on inspect errors and warn on drain timeout

commit
86ac292d5a5525c82815f286a626cc35e4025676
author
bnema · · 2026-02-07 21:23 UTC
parent
17b7bd9
signature
unsigned
2 files · +11 -2
M docs/config/index.md +1 -1
@@ -61,7 +61,7 @@ health_timeout = "90s" # Max wait for health-based readiness
6161 readiness_delay = "5s" # Wait after running before ready
6262 drain_mode = "auto" # auto, inflight, delay
6363 drain_timeout = "30s" # Max wait for in-flight drain
64-drain_delay = "2s" # Wait after proxy invalidation before old stop
64+drain_delay = "2s" # Wait after proxy invalidation before stopping the old container
6565
6666 # Logging
6767 [logging]
@@ -61,7 +61,7 @@ health_timeout = "90s" # Max wait for health-based readiness
61 readiness_delay = "5s" # Wait after running before ready61 readiness_delay = "5s" # Wait after running before ready
62 drain_mode = "auto" # auto, inflight, delay62 drain_mode = "auto" # auto, inflight, delay
63 drain_timeout = "30s" # Max wait for in-flight drain63 drain_timeout = "30s" # Max wait for in-flight drain
64-drain_delay = "2s" # Wait after proxy invalidation before old stop64+drain_delay = "2s" # Wait after proxy invalidation before stopping the old container
65 65
66 # Logging66 # Logging
67 [logging]67 [logging]
M internal/usecase/container/service.go +10 -1
@@ -353,6 +353,7 @@ func (s *Service) createStartedContainer(ctx context.Context, route domain.Route
353353
354354 inspected, err := s.runtime.InspectContainer(ctx, newContainer.ID)
355355 if err != nil {
356+ s.cleanupFailedContainer(ctx, newContainer.ID)
356357 return nil, log.WrapErr(err, "failed to inspect started container")
357358 }
358359
@@ -396,6 +397,8 @@ func (s *Service) finalizePreviousContainer(ctx context.Context, domainName stri
396397 }
397398
398399 func (s *Service) waitForDrain(ctx context.Context, oldContainerID string) {
400+ log := zerowrap.FromCtx(ctx)
401+
399402 s.mu.RLock()
400403 cfg := s.config
401404 waiter := s.drainWaiter
@@ -412,7 +415,13 @@ func (s *Service) waitForDrain(ctx context.Context, oldContainerID string) {
412415 if timeout == 0 {
413416 timeout = 30 * time.Second
414417 }
415- waiter.WaitForNoInFlight(ctx, oldContainerID, timeout)
418+ drained := waiter.WaitForNoInFlight(ctx, oldContainerID, timeout)
419+ if !drained {
420+ log.Warn().
421+ Str("old_container_id", oldContainerID).
422+ Dur("drain_timeout", timeout).
423+ Msg("drain wait timed out; old container may still have in-flight traffic")
424+ }
416425 return
417426 }
418427
@@ -353,6 +353,7 @@ func (s *Service) createStartedContainer(ctx context.Context, route domain.Route
353 353
354 inspected, err := s.runtime.InspectContainer(ctx, newContainer.ID)354 inspected, err := s.runtime.InspectContainer(ctx, newContainer.ID)
355 if err != nil {355 if err != nil {
356+ s.cleanupFailedContainer(ctx, newContainer.ID)
356 return nil, log.WrapErr(err, "failed to inspect started container")357 return nil, log.WrapErr(err, "failed to inspect started container")
357 }358 }
358 359
@@ -396,6 +397,8 @@ func (s *Service) finalizePreviousContainer(ctx context.Context, domainName stri
396 }397 }
397 398
398 func (s *Service) waitForDrain(ctx context.Context, oldContainerID string) {399 func (s *Service) waitForDrain(ctx context.Context, oldContainerID string) {
400+ log := zerowrap.FromCtx(ctx)
401+
399 s.mu.RLock()402 s.mu.RLock()
400 cfg := s.config403 cfg := s.config
401 waiter := s.drainWaiter404 waiter := s.drainWaiter
@@ -412,7 +415,13 @@ func (s *Service) waitForDrain(ctx context.Context, oldContainerID string) {
412 if timeout == 0 {415 if timeout == 0 {
413 timeout = 30 * time.Second416 timeout = 30 * time.Second
414 }417 }
415- waiter.WaitForNoInFlight(ctx, oldContainerID, timeout)418+ drained := waiter.WaitForNoInFlight(ctx, oldContainerID, timeout)
419+ if !drained {
420+ log.Warn().
421+ Str("old_container_id", oldContainerID).
422+ Dur("drain_timeout", timeout).
423+ Msg("drain wait timed out; old container may still have in-flight traffic")
424+ }
416 return425 return
417 }426 }
418 427

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close