Skip to content

Instantly share code, notes, and snippets.

@LLFourn
Last active August 10, 2023 13:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LLFourn/70b70b7e26b5e57de7894eefee3c97e1 to your computer and use it in GitHub Desktop.
Save LLFourn/70b70b7e26b5e57de7894eefee3c97e1 to your computer and use it in GitHub Desktop.
Docker, Getting around PID 1
# So you want to pipe shell scripts to docker. But docker runs the container's command as PID 1.
# Linux doesn't set up signal handlers for PID 1.
# This gives you the following problems:
# 1. The script can't kill itself:
echo 'kill $$; echo "Still alive"' | docker run -i --rm alpine sh
# 2. You can't kill the script from outside with a normal kill:
echo "sleep 1000" | docker run -i --rm alpine sh & sleep 1 && kill $!
docker ps # it didn't die
#==== Solving problem 1 ====
# We need to run out script as a PID other than PID 1. This usually works:
echo 'kill $$; echo "Still alive!"'| docker run -i --rm alpine sh -c sh
# But for things with bash as /bin/sh you need another trick:
echo 'kill $$; echo "Still alive"'| docker run -i --rm centos sh -c ':;sh' #<-- take note
#==== Solving problem 2 ====
# In order to make it killable from the outside we need to set up a signal hanlder in PID 1
echo 'trap "exit 0" TERM; while true; do sleep 1; done' | docker run -i --rm alpine sh &
kill $!
docker ps # it should be dead (soon at least)
# But this only kinda works...
# It won't respond to the signal while it's doing sleep(1) or stuck in any other command
#==== Solving problem 1 & 2 togther properly ====
# We need to have the script running as non-PID1 and we need to have a signal handler that will
# run immediately
# Solution:
docker run -i --rm alpine sh -c 'trap "exit 0" TERM; sh <&0 & wait $!'
# Problem 1
echo 'kill $$; echo "Still alive"' | docker run -i --rm alpine sh -c 'trap "exit 0" TERM; sh <&0 & wait $!'
# Problem 2
echo 'sleep 10000' | docker run -i --rm centos sh -c 'trap "exit 0" TERM; sh <&0 & wait $!' &
kill $!
docker ps # it's dead
# If you want to you can even forward the signal inside the container to your script's process:
echo 'sleep 10000' | docker run -i --rm centos sh -c 'trap "kill \$!" TERM; sh <&0 & wait $!' &
#==== 💀FINAL BOSS💀 ====
#...But apparently sh <&0 only works on bash ie centos 🤦😩.
# So we need to create a fifo and delete it at EXIT. This works on all platforms I've tested:
echo 'sleep 10000' | docker run -i --rm alpine sh -c \
'mkfifo stdin; trap "kill \$!" TERM; trap "rm stdin" EXIT; sh<stdin & cat>stdin; wait $!' &
kill $!
docker ps
# It's gone!! 🎉
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment