Docker: bug in ubuntu 24.04 when stoping a container
When you stop a container, docker will send a signal to your process. There are many blog posts, Stack Overflow questions and recommended way on the internet, but if you're using an Ubuntu-based OS, you may find out there is no single solution that works. Your container will not received the message and it will be killed after the default 10 seconds.
If you want to test this, just run the following in a terminal:
docker run -d --rm --init --name dont-kill-me node:lts-alpine node -e "
const interval = setInterval(() => {}, 1000);
process.on('SIGTERM', () => clearInterval(interval));
"
And in another one:
time docker stop dont-kill-me
You will get the following output:
# time docker stop dont-kill-me
dont-kill-me
real 0m12,170s
user 0m0,012s
sys 0m0,014s
The culprit is AppArmor! Well, not directly, but there is a bug in the profile definition that prevents Docker from sending the signal. You can see this with dmesg
:
[ +18,458038] audit: type=1400 audit(1726477289.904:399): apparmor="DENIED" operation="signal" class="signal" profile="docker-default" pid=45199 comm="runc" requested_mask="receive" denied_mask="receive" signal=term peer="runc"
Fortunately, there is a solution. You can see it in the fourth comment of the following bug report: https://bugs.launchpad.net/ubuntu/+source/docker.io/+bug/2063099. The upstream project has already fixed the issue, but in the meantime, you can apply this workaround:
Create a file in /etc/apparmor.d/docker-default
with the following content:
#include <tunables/global>
profile docker-default flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network,
capability,
file,
umount,
# Host (privileged) processes may send signals to container processes.
signal (receive) peer=unconfined,
# dockerd may send signals to container processes (for "docker kill").
signal (receive) peer=unconfined,
# runc may send signals to container processes
signal (receive) peer=runc,
# Container processes may send signals amongst themselves.
signal (send,receive) peer=docker-default,
deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
# deny write to files not in /proc/<number>/** or /proc/sys/**
deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9/]*}/** w,
deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/kcore rwklx,
deny mount,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/** rwklx,
deny /sys/devices/virtual/powercap/** rwklx,
deny /sys/kernel/security/** rwklx,
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
ptrace (trace,read,tracedby,readby) peer=docker-default,
}
And run the following command to apply the modification immediately (or reboot but we're using a Unix system, we don't need to reboot all the time):
sudo apparmor_parser -Kr /etc/apparmor.d/docker-default
That's it, your container should receive the signal as expected:
# time docker stop dont-kill-me
dont-kill-me
real 0m0,155s
user 0m0,012s
sys 0m0,016s