From 4f87fb7220d13363e4c965da37d5c0803e935205 Mon Sep 17 00:00:00 2001 From: ZeroKnight Date: Sat, 6 Dec 2025 15:00:35 -0800 Subject: [PATCH] fix(docker): app and sidekiq containers ignore signals Typically, when attempting to stop a container via `docker stop` or `podman stop`, the container engine will send a stop signal (SIGTERM by default) to the container's main process. There are two common ways this can go wrong: 1. The main process is run as PID 1 and doesn't register a signal handler for the stop signal and is consequently ignored 2. The main process is a shell script running a foreground process with no `trap`s and is consequently ignored by the *shell* In either case, because the graceful stop signal is ignored, the container engine will then send a `SIGKILL` to the container process after a default timeout of 10 seconds. This is why some containers can be observed to "hang" when being stopped when they have no other reason to do so. Unlike `SIGTERM` or `SIGINT`, `SIGKILL` is an immediate, ungraceful stop that doesn't give the process time to clean up. There is a fair bit of nuance in how `sh` and `bash` handle signals in different circumstances. The behavior relevant to the second case above and Dawarich's entrypoints in particular is that the shell ignores signals like `SIGTERM` and `SIGINT` when waiting on a foreground job; in this case, that would be: `bundle exec ${@}`. The reason that `SIGINT` is not ignored after pressing `Ctrl+C` while running the docker compose stack is because in that case the shell is **interactive** and the shell *does* respond to `SIGINT` then (c.f. the aforementioned nuance). Thankfully, the fix is simple: `exec` the main process, which causes the server process to *replace* the shell process and directly receive any signals sent. Additionally, the stop signal for the app process should be set to `SIGINT`, as that is the expected signal for graceful shutdown. Sidekiq is fine with either `SIGTERM` or `SIGINT`, which is convenient. --- docker/Dockerfile | 2 ++ docker/sidekiq-entrypoint.sh | 2 +- docker/web-entrypoint.sh | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 05d52723..1fd1a5e0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -80,4 +80,6 @@ RUN chmod +x /usr/local/bin/sidekiq-entrypoint.sh EXPOSE $RAILS_PORT +STOPSIGNAL SIGINT + ENTRYPOINT [ "bundle", "exec" ] diff --git a/docker/sidekiq-entrypoint.sh b/docker/sidekiq-entrypoint.sh index b55f3ff0..d137874a 100644 --- a/docker/sidekiq-entrypoint.sh +++ b/docker/sidekiq-entrypoint.sh @@ -33,4 +33,4 @@ done echo "✅ PostgreSQL is ready!" # run sidekiq -bundle exec sidekiq +exec bundle exec sidekiq diff --git a/docker/web-entrypoint.sh b/docker/web-entrypoint.sh index 8785446b..12875a25 100644 --- a/docker/web-entrypoint.sh +++ b/docker/web-entrypoint.sh @@ -83,4 +83,4 @@ echo "Running seeds..." bundle exec rails db:seed # run passed commands -bundle exec ${@} +exec bundle exec ${@}