Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active October 14, 2024 07:00
Show Gist options
  • Save x-yuri/4a0191a1c66676691c06ea869de8484a to your computer and use it in GitHub Desktop.
Save x-yuri/4a0191a1c66676691c06ea869de8484a to your computer and use it in GitHub Desktop.
docker run: determining if --init is needed

docker run: determining if --init is needed

Normally, if a program doesn't set a SIGTERM handler, it needs --init:

Dockerfile:

FROM alpine:3.20
COPY a.c .
RUN apk add build-base \
    && gcc a.c

a.c:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
void handler(int sig) {
    printf("shutting down...\n");
    exit(0);
}
int main(int argc, char *argv[]) {
    if (argc > 1 && strcmp(argv[1], "--set-signal-handler") == 0)
        signal(SIGTERM, handler);
    for (;;) sleep(INT_MAX);
    return EXIT_SUCCESS;
}
$ docker build -t i .

$ docker run --rm i ./a.out
$ docker exec `docker ps -q | head -1` pkill a.out
// it doesn't stop
$ docker kill -s KILL `docker ps -q | head -1`

$ docker run --rm i ./a.out --set-signal-handler
$ docker exec `docker ps -q | head -1` pkill a.out
shutting down...

$ docker run --rm --init i ./a.out
$ docker exec `docker ps -q | head -1` pkill a.out

$ docker run --rm --init i ./a.out --set-signal-handler
$ docker exec `docker ps -q | head -1` pkill a.out
shutting down...

python:

$ docker run --rm python:3.13.0-alpine3.20 python -c 'import time; time.sleep(1000)'
$ docker exec `docker ps -q | head -1` pkill python
// it doesn't stop
$ docker kill -s KILL `docker ps -q | head -1`

$ docker run --rm python:3.13.0-alpine3.20 \
    python -c 'import signal; import time; import sys; signal.signal(signal.SIGTERM, lambda signum, frame: (print("shutting down..."), sys.exit(0))); time.sleep(1000)'
$ docker exec `docker ps -q | head -1` pkill python
shutting down...

$ docker run --rm --init python:3.13.0-alpine3.20 python -c 'import time; time.sleep(1000)'
$ docker exec `docker ps -q | head -1` pkill python

$ docker run --rm --init python:3.13.0-alpine3.20 \
    python -c 'import signal; import time; import sys; signal.signal(signal.SIGTERM, lambda signum, frame: (print("shutting down..."), sys.exit(0))); time.sleep(1000)'
$ docker exec `docker ps -q | head -1` pkill python
shutting down...

perl:

$ docker run --rm perl:5.41.4-slim perl -e 'sleep'
$ docker kill -s TERM `docker ps -q | head -1`
// it doesn't stop
$ docker kill -s KILL `docker ps -q | head -1`

$ docker run --rm perl:5.41.4-slim \
    perl -e '$SIG{TERM} = sub { print "shutting down..."; exit 0 }; sleep'
$ docker kill -s TERM `docker ps -q | head -1`
shutting down...

$ docker run --rm --init perl:5.41.4-slim perl -e 'sleep'
$ docker kill -s TERM `docker ps -q | head -1`

$ docker run --rm --init perl:5.41.4-slim \
    perl -e '$SIG{TERM} = sub { print "shutting down..."; exit 0 }; sleep'
$ docker kill -s TERM `docker ps -q | head -1`
shutting down...

But there are languages that set their own handlers before execution of user code starts.

ruby:

$ docker run --rm ruby:3.3.5-alpine3.20 ruby -e 'sleep'
$ docker exec `docker ps -q | head -1` pkill ruby

$ docker run --rm ruby:3.3.5-alpine3.20 ruby -e 'Signal.trap("TERM") do puts "shutting down..."; exit end; sleep'
$ docker exec `docker ps -q | head -1` pkill ruby
shutting down...

$ docker run --rm --init ruby:3.3.5-alpine3.20 ruby -e 'sleep'
$ docker exec `docker ps -q | head -1` pkill ruby

$ docker run --rm --init ruby:3.3.5-alpine3.20 ruby -e 'Signal.trap("TERM") do puts "shutting down..."; exit end; sleep'
$ docker exec `docker ps -q | head -1` pkill ruby
shutting down...

go:

Dockerfile:

FROM alpine:3.20
COPY a.go .
RUN apk add go && GO111MODULE=off go build

a.go:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    if len(os.Args) > 1 && os.Args[1] == "--set-signal-handler" {
        sigChan := make(chan os.Signal, 1)
        signal.Notify(sigChan, syscall.SIGTERM)
        go func() {
            <-sigChan
            fmt.Printf("shutting down...\n")
            os.Exit(0)
        }()
    }
    for {
        time.Sleep(1 * time.Second)
    }
}
$ docker build -t i -f Dockerfile2 .

$ docker run --rm i ./_
$ docker exec `docker ps -q | head -1` pkill _

$ docker run --rm i ./_ --set-signal-handler
$ docker exec `docker ps -q | head -1` pkill _
shutting down...

$ docker run --rm --init i ./_
$ docker exec `docker ps -q | head -1` pkill _

$ docker run --rm --init i ./_ --set-signal-handler
$ docker exec `docker ps -q | head -1` pkill _
shutting down...

So with ruby and go programs you probably won't have problems. If a program doesn't set a SIGTERM handler, then the cleanup is apparently not needed, the language runtime will set a handler and it'll terminate on SIGTERM. Otherwise the program's SIGTERM handler will terminate it.

In languages where it's not the case to determine if you need --init run the program in a container, make sure it's running under PID 1 and send SIGTERM to it. If it terminates, then --init apparently is not needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment