The webapp wrapper's container ignores SIGTERM, so every stop waits out Docker's full 10s grace period and the process is then SIGKILLed.
Evidence
From a stack Deploy Test CI run — the frontend (webapp wrapper) vs. the backend (node-service wrapper) in the same deployment:
02:41:25.7888 Container ...-frontend-1 Stopping
02:41:35.9030 Container ...-frontend-1 Stopped <- 10.1s (grace period expired)
02:41:26.4016 Container ...-backend-1 Stopping
02:41:26.4017 Container ...-backend-1 Stopped <- 0.6s (clean shutdown)
Cause
webapp-base/Containerfile sets CMD ["/scripts/start-serving-app.sh"], so the bash script is PID 1. The last line of that script starts the server as a child process:
http-server $STACK_HTTP_EXTRA_ARGS -p ${STACK_LISTEN_PORT} "${STACK_WEBAPP_FILES_DIR}"
Bash as PID 1 does not forward signals to children, and while waiting on a foreground child it defers the trap until that child exits. So SIGTERM goes to bash and nothing happens; Docker waits 10s, then SIGKILLs the container.
node-service-base/scripts/start-service.sh already does this correctly — it execs in every branch, which is exactly why the backend above stops cleanly. This is a good contrast: the fix is to match it.
Affected
webapp-base/scripts/start-serving-app.sh — final http-server invocation
nextjs-base/scripts/start-serving-app.sh — final $STACK_BUILD_TOOL start . -- -p ... invocation has the same shape (not directly observed failing, but same pattern)
Not affected: node-service-base (already execs).
Suggested fix
exec the server so it becomes PID 1 and receives signals directly:
exec http-server $STACK_HTTP_EXTRA_ARGS -p ${STACK_LISTEN_PORT} "${STACK_WEBAPP_FILES_DIR}"
Worth checking whether http-server itself handles SIGTERM promptly once it is PID 1; if not, a small init (tini) or an explicit trap would be needed instead.
Impact
Cosmetic in the sense that nothing breaks, but it adds ~10s to every stop/restart of a webapp-wrapped container, which is noticeable in the stack test suites, and it means the server is always killed rather than shut down cleanly — no chance to finish in-flight requests or flush.
Provenance
Noticed while diagnosing unrelated stack Deploy Test failures (the frontend's Vite migration changed the served bundle path). Not investigated further beyond reading the scripts.
The
webappwrapper's container ignoresSIGTERM, so every stop waits out Docker's full 10s grace period and the process is thenSIGKILLed.Evidence
From a stack Deploy Test CI run — the frontend (webapp wrapper) vs. the backend (node-service wrapper) in the same deployment:
Cause
webapp-base/ContainerfilesetsCMD ["/scripts/start-serving-app.sh"], so the bash script is PID 1. The last line of that script starts the server as a child process:Bash as PID 1 does not forward signals to children, and while waiting on a foreground child it defers the trap until that child exits. So
SIGTERMgoes to bash and nothing happens; Docker waits 10s, thenSIGKILLs the container.node-service-base/scripts/start-service.shalready does this correctly — itexecs in every branch, which is exactly why the backend above stops cleanly. This is a good contrast: the fix is to match it.Affected
webapp-base/scripts/start-serving-app.sh— finalhttp-serverinvocationnextjs-base/scripts/start-serving-app.sh— final$STACK_BUILD_TOOL start . -- -p ...invocation has the same shape (not directly observed failing, but same pattern)Not affected:
node-service-base(alreadyexecs).Suggested fix
execthe server so it becomes PID 1 and receives signals directly:Worth checking whether
http-serveritself handlesSIGTERMpromptly once it is PID 1; if not, a small init (tini) or an explicit trap would be needed instead.Impact
Cosmetic in the sense that nothing breaks, but it adds ~10s to every stop/restart of a webapp-wrapped container, which is noticeable in the stack test suites, and it means the server is always killed rather than shut down cleanly — no chance to finish in-flight requests or flush.
Provenance
Noticed while diagnosing unrelated stack Deploy Test failures (the frontend's Vite migration changed the served bundle path). Not investigated further beyond reading the scripts.