Can an unprivileged docker container be paused from the inside? Can an unprivileged docker container be paused from the inside? docker docker

Can an unprivileged docker container be paused from the inside?


TL;DR;

On a linux container, the answer is definitely, yes, because those two are equivalent:

  • From host:
    docker pause [container-id]
  • From the container:
    kill -SIGSTOP [process(es)-id]
    or, even shorter
    kill -SIGSTOP -1 
    Mind that:
    1. If your process ID, or PID is 1, then you fall under an edge case, because PID 1, the init process, do have a specific meaning and behaviour in Linux.
    2. Some processes might spawn child worker, as the NGINX example below.

And those two are also equivalent:

  • From host:
    docker unpause [container-id]
  • From the container:
    kill -SIGCONT [process(es)-id]
    or, even shorter
    kill -SIGCONT -1 

Also mind that, in some edges cases, this won't work. The edge cases being that your process is meant to catch those two signals, SIGSTOP and SIGCONT, and ignore them.

In those cases, you will have to

  • either, be a privileged user, because the use of the cgroup freezer is under a folder, that is per default, read only in Docker, and probably this will end you in a dead end, because you will not be able to jump in the container anymore.
  • or, run your container with the flag --init so the PID 1 will just be a wrapper process initialised by Docker and you won't need to pause it anymore in order to pause the processes running inside your container.

    You can use the --init flag to indicate that an init process should be used as the PID 1 in the container. Specifying an init process ensures the usual responsibilities of an init system, such as reaping zombie processes, are performed inside the created container.

    The default init process used is the first docker-init executable found in the system path of the Docker daemon process. This docker-init binary, included in the default installation, is backed by tini.


This is definitely possible for Linux containers, and is explained, somehow, in the documentation, where they point out that running docker pause [container-id] just means that Docker will use an equivalent mechanism to sending the SIGSTOP signal to the process run in your container.

The docker pause command suspends all processes in the specified containers. On Linux, this uses the freezer cgroup. Traditionally, when suspending a process the SIGSTOP signal is used, which is observable by the process being suspended. With the freezer cgroup the process is unaware, and unable to capture, that it is being suspended, and subsequently resumed. On Windows, only Hyper-V containers can be paused.

See the freezer cgroup documentation for further details.

Source: https://docs.docker.com/engine/reference/commandline/pause/

Here would be an example on an NGINX Alpine container:

### For now, we are on the host machine$ docker run -p 8080:80 -d nginx:alpinef444eaf8464e30c18f7f83bb0d1bd07b48d0d99f9d9e588b2bd77659db520524### Testing if NGINX answers, successful $ curl -I -m 1  http://localhost:8080/ HTTP/1.1 200 OKServer: nginx/1.19.0Date: Sun, 28 Jun 2020 11:49:33 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Tue, 26 May 2020 15:37:18 GMTConnection: keep-aliveETag: "5ecd37ae-264"Accept-Ranges: bytes### Jumping into the container$ docker exec -ti f7a2be0e230b9f7937d90954ef03502993857c5081ab20ed9a943a35687fbca4 ash### This is the container, now, let's see the processes running/ # ps -o pid,vsz,rss,tty,stat,time,ruser,argsPID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND    1 6000 4536 ?      S     0:00 root     nginx: master process nginx -g daemon off;   29 6440 1828 ?      S     0:00 nginx    nginx: worker process   30 6440 1828 ?      S     0:00 nginx    nginx: worker process   31 6440 1828 ?      S     0:00 nginx    nginx: worker process   32 6440 1828 ?      S     0:00 nginx    nginx: worker process   49 1648 1052 136,0  S     0:00 root     ash   55 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args### Now let's send the SIGSTOP signal to the workers of NGINX, as docker pause would do/ # kill -SIGSTOP 29 30 31 32### Running ps again just to observer the T (stopped) state of the processes/ # ps -o pid,vsz,rss,tty,stat,time,ruser,argsPID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND    1 6000 4536 ?      S     0:00 root     nginx: master process nginx -g daemon off;   29 6440 1828 ?      T     0:00 nginx    nginx: worker process   30 6440 1828 ?      T     0:00 nginx    nginx: worker process   31 6440 1828 ?      T     0:00 nginx    nginx: worker process   32 6440 1828 ?      T     0:00 nginx    nginx: worker process   57 1648 1052 136,0  S     0:00 root     ash   63 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args/ # exit### Back on the host to confirm NGINX doesn't answer anymore$ curl -I -m 1  http://localhost:8080/ curl: (28) Operation timed out after 1000 milliseconds with 0 bytes received$ docker exec -ti f7a2be0e230b9f7937d90954ef03502993857c5081ab20ed9a943a35687fbca4 ash### Sending the SIGCONT signal as docker unpause would do/ # kill -SIGCONT 29 30 31 32/ # ps -o pid,vsz,rss,tty,stat,time,ruser,argsPID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND    1 6000 4536 ?      S     0:00 root     nginx: master process nginx -g daemon off;   29 6440 1828 ?      S     0:00 nginx    nginx: worker process   30 6440 1828 ?      S     0:00 nginx    nginx: worker process   31 6440 1828 ?      S     0:00 nginx    nginx: worker process   32 6440 1828 ?      S     0:00 nginx    nginx: worker process   57 1648 1052 136,0  S     0:00 root     ash   62 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args 29 30 31 32/ # exit### Back on the host to confirm NGINX is back$ curl -I http://localhost:8080/       HTTP/1.1 200 OKServer: nginx/1.19.0Date: Sun, 28 Jun 2020 11:56:23 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Tue, 26 May 2020 15:37:18 GMTConnection: keep-aliveETag: "5ecd37ae-264"Accept-Ranges: bytes

For the cases where the meaningful process is the PID 1 and so, is protected by the Linux kernel, you might want to try the --init flag at the run of your container so Docker will create a wrapper process that will be able to pass the signal to your application.

$ docker run -p 8080:80 -d --init nginx:alpine                                        e61e9158b2aab95007b97aa50bc77fff6b5c15cf3b16aa20a486891724bec6e9$ docker exec -ti e61e9158b2aab95007b97aa50bc77fff6b5c15cf3b16aa20a486891724bec6e9 ash/ # ps -o pid,vsz,rss,tty,stat,time,ruser,argsPID   VSZ  RSS  TT     STAT TIME  RUSER    COMMAND    1 1052    4 ?      S     0:00 root     /sbin/docker-init -- /docker-entrypoint.sh nginx -g daemon off;    7 6000 4320 ?      S     0:00 root     nginx: master process nginx -g daemon off;   31 6440 1820 ?      S     0:00 nginx    nginx: worker process   32 6440 1820 ?      S     0:00 nginx    nginx: worker process   33 6440 1820 ?      S     0:00 nginx    nginx: worker process   34 6440 1820 ?      S     0:00 nginx    nginx: worker process   35 1648    4 136,0  S     0:00 root     ash   40 1576    4 136,0  R     0:00 root     ps -o pid,vsz,rss,tty,stat,time,ruser,args

See how nginx: master process nginx -g daemon off; that was PID 1 in the previous use case became PID 7, now?
This ables us to kill -SIGSTOP -1 and be sure all meaningful processes are stopped, still we won't be locked out of the container.


While digging on this, I found this blog post that seems like a good read on the topic: https://major.io/2009/06/15/two-great-signals-sigstop-and-sigcont/

Also related it the ps manual page extract about process state code:

  Here are the different values that the s, stat and state output  specifiers (header "STAT" or "S") will display to describe the state  of a process:          D    uninterruptible sleep (usually IO)          I    Idle kernel thread          R    running or runnable (on run queue)          S    interruptible sleep (waiting for an event to complete)          T    stopped by job control signal          t    stopped by debugger during the tracing          W    paging (not valid since the 2.6.xx kernel)          X    dead (should never be seen)          Z    defunct ("zombie") process, terminated but not reaped by               its parent  For BSD formats and when the stat keyword is used, additional  characters may be displayed:          <    high-priority (not nice to other users)          N    low-priority (nice to other users)          L    has pages locked into memory (for real-time and custom               IO)          s    is a session leader          l    is multi-threaded (using CLONE_THREAD, like NPTL               pthreads do)          +    is in the foreground process group

Source https://man7.org/linux/man-pages/man1/ps.1.html#PROCESS_STATE_CODES


docker pause command from inside is not possible for an unprivileged container. It would need access to the docker daemon by mounting the socket.

You would need to build a custom solution. Just the basic idea: You could bindmount a folder from the host. Inside this folder you create a file which acts as a lock. So when you pause inside the container you would create the file. While the file exists you activly wait/sleep. As soon as the host would delete the file at path which was mounted, your code would resume. That is a rather naive approach because you actively wait, but it should do the trick.

You can also look into inotify to overcome activ waiting.https://lwn.net/Articles/604686/