Docker 7 🐳 Managing Container Lifecycles: Pause, Unpause, Restart, and Signal Handling (SIGTERM, SIGKILL)
A container’s lifecycle is not limited to starting and stopping. Docker provides operations for pausing a running container without terminating it, resuming a paused container, restarting a container with a fresh process, and controlling the signals that govern how a container’s main process terminates. These operations matter because they determine what happens to in-flight work, open connections, and state when a container’s life is interrupted. The difference between a graceful shutdown and an abrupt kill is the difference between a clean database commit and a corrupted file.
Signal handling is where this becomes concrete. When Docker stops a container, it sends SIGTERM to the process with PID 1 inside the container. If that process does not exit within a grace period, Docker sends SIGKILL, which cannot be caught and terminates the process immediately. Whether the application handles SIGTERM determines whether it can flush buffers, close connections, and save state before dying. Many containers run shells or wrappers that do not forward signals, causing the application to never receive SIGTERM and to be killed after the timeout. This is one of the most common operational problems with Dockerized applications.
This chapter covers pause and unpause, restart policies and the restart command, the SIGTERM/SIGKILL sequence, PID 1 and signal forwarding, the --init flag, stop signals and timeouts, and the patterns for building containers that shut down gracefully.
Key point: docker pause freezes a container’s processes using the cgroup freezer, and docker unpause resumes them. docker stop sends SIGTERM (or a custom signal) to PID 1, waits a grace period (default 10 seconds), then sends SIGKILL. The container’s main process must handle SIGTERM and forward it to children for graceful shutdown to work.
Why lifecycle management matters
The data-integrity problem. A database that receives SIGKILL mid-transaction may leave its data files inconsistent. An application that is writing a log or a file may truncate it. A message queue consumer that is processing a message may lose it. Graceful shutdown allows the application to complete the current operation, roll back partial work, and release resources before exiting.
The availability problem. When a container stops ungracefully, in-flight requests fail and connections drop. In a load-balanced environment, a container that shuts down gracefully can finish its current requests while the load balancer drains it, so no client sees an error.
The restart-loop problem. A container with a restart policy that keeps crashing will restart repeatedly. Understanding why it exits, whether the exit is clean, and what the exit code means is the difference between diagnosing a misconfiguration and watching the container restart forever.
The signal-forwarding problem. Containers run their main process as PID 1. PID 1 has special semantics in Linux: it does not receive default signal handling, and it is responsible for reaping orphaned child processes. If the main process is a shell script that spawns the application as a child, the shell may not forward SIGTERM, and the application never learns that it should shut down. This is why docker stop sometimes results in a SIGKILL after the timeout.
The resource-freezing problem. Pausing a container is not the same as stopping it. A paused container keeps its memory, open files, and network connections but is frozen by the kernel. This is useful for temporarily suspending a workload without losing its state, but it must be paired with unpause to resume.
a. Pausing and unpausing containers
docker pause suspends all processes in a container using the cgroup freezer. The processes are stopped immediately, and their state is preserved in memory.
docker pause web
The container remains in the docker ps list with the status Up (Paused). Its memory is retained, its network connections remain open, and its process tree is frozen. The container is not consuming CPU while paused.
docker unpause resumes a paused container:
docker unpause web
The processes continue from where they stopped. Because the pause is at the kernel level, the application does not need any special handling; it experiences the pause as if time had stopped.
Pausing is useful for temporarily freeing CPU without losing state, for coordinating multi-container operations, and for debugging. It is not a substitute for stopping: a paused container still holds its memory, its port bindings, and its volumes.
A paused container cannot be stopped or removed directly. It must be unpaused first, or the command must be forced. This is because the container’s processes are frozen and cannot receive the signals that stop and remove would send.
b. Restart policies and the restart command
A restart policy tells Docker what to do when a container’s main process exits. The policy is set at container creation with --restart.
| Policy | Behavior |
|---|---|
no | Never restart (default) |
on-failure[:max] | Restart only on non-zero exit, up to max attempts |
always | Always restart, including on daemon restart |
unless-stopped | Always restart unless explicitly stopped |
The on-failure policy restarts the container when the process exits with a non-zero code. This is useful for services that crash and should be retried. The optional :max limits the number of retries, after which the container stays stopped.
docker run -d --restart on-failure:5 --name web nginx
The always policy restarts the container whenever it exits, including when the Docker daemon restarts. This is the aggressive option; a container that crashes on startup will restart forever.
docker run -d --restart always --name web nginx
The unless-stopped policy is like always but does not restart a container that was explicitly stopped with docker stop. This is often the better choice for production services because it respects an operator’s decision to stop the container.
docker run -d --restart unless-stopped --name web nginx
The docker restart command stops and starts a container in one operation:
docker restart web
The command sends SIGTERM, waits for the grace period, then starts the container again. The -t flag sets the timeout:
docker restart -t 30 web
A restart is useful for applying configuration changes that are read at startup, or for recovering from a transient failure.
The restart policy can be changed on a running container with docker update:
docker update --restart unless-stopped web
This is useful for adopting a restart policy on containers that were created without one.
c. The SIGTERM and SIGKILL sequence
When docker stop is called, Docker sends a signal to the container’s main process. The default signal is SIGTERM, which is a request to terminate gracefully. The process can catch it, perform cleanup, and exit on its own schedule.
docker stop web
After sending SIGTERM, Docker waits for a grace period. The default is 10 seconds, configurable with -t:
docker stop -t 60 web
If the process has not exited when the grace period expires, Docker sends SIGKILL:
SIGTERM → wait (10 seconds) → SIGKILL → wait → container stops
SIGKILL cannot be caught, blocked, or ignored. The process is terminated immediately by the kernel, with no opportunity for cleanup. This is the last resort.
The sequence is the same for docker restart and for the termination phase of docker rm -f, except that docker rm -f may skip the grace period depending on the version.
A container that takes 10 seconds to stop is a container whose main process does not handle SIGTERM. A container that stops immediately is one that does. The exit code reflects what happened: exit code 0 means a clean exit, 143 means terminated by SIGTERM (128 + 15), and 137 means terminated by SIGKILL (128 + 9).
docker ps -a --format "table {{.Names}}\t{{.Status}}"
# web Exited (0) 2 minutes ago → clean exit
# api Exited (143) 2 minutes ago → SIGTERM, handled or default
# db Exited (137) 2 minutes ago → SIGKILL, forced
d. PID 1, signal forwarding, and the init process
Inside a container, the main process runs as PID 1. PID 1 has special semantics: the kernel does not apply default signal handling to it, so a PID 1 process that does not explicitly install a handler for a signal will ignore it.
This matters for shell scripts and wrappers. A common Dockerfile pattern is:
CMD /app/start.sh
where start.sh runs the actual application:
#!/bin/sh
nginx -g 'daemon off;'
When Docker sends SIGTERM, it goes to the shell script (PID 1), not to nginx. The shell does not forward the signal, so nginx never learns to shut down. After the timeout, Docker sends SIGKILL, and the container dies abruptly.
The fix is either to run the application directly as PID 1:
CMD ["nginx", "-g", "daemon off;"]
or to use the exec form in a shell script:
#!/bin/sh
exec nginx -g 'daemon off;'
The exec replaces the shell process with nginx, so nginx becomes PID 1 and receives the signals.
For cases where a wrapper is necessary, Docker provides the --init flag, which inserts a tiny init process (tini) as PID 1:
docker run -d --init --name web nginx
The init process forwards signals to the child and reaps orphaned processes. This is the recommended approach when the application cannot be run directly as PID 1.
e. Custom stop signals and timeouts
Some applications expect a different signal. A Node.js application might listen for SIGINT. A process might expect SIGHUP to reload configuration. The --stop-signal flag sets the signal that docker stop sends:
docker run -d --stop-signal SIGINT --name app myapp
The signal can be a name (SIGINT, SIGTERM) or a number (2, 15). The docker stop -s flag overrides it for a single stop:
docker stop -s SIGHUP web
The --stop-timeout flag sets the default grace period for a container:
docker run -d --stop-timeout 30 --name db postgres
The -t flag on docker stop overrides it for a single operation:
docker stop -t 5 web
A short timeout is appropriate for containers that shut down quickly; a long timeout is appropriate for databases and services that need time to flush data.
Complete Example Session
# ============================================
# PART 1: RUN A CONTAINER WITH A RESTART POLICY
# ============================================
docker run -d --name web --restart unless-stopped -p 8080:80 nginx
# ============================================
# PART 2: PAUSE THE CONTAINER
# ============================================
docker pause web
docker ps --filter name=web
# Status: Up X seconds (Paused)
# ============================================
# PART 3: UNPAUSE THE CONTAINER
# ============================================
docker unpause web
docker ps --filter name=web
# Status: Up X seconds
# ============================================
# PART 4: RESTART THE CONTAINER
# ============================================
docker restart web
# ============================================
# PART 5: INSPECT THE RESTART POLICY
# ============================================
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' web
# unless-stopped
# ============================================
# PART 6: CHANGE THE RESTART POLICY
# ============================================
docker update --restart always web
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' web
# always
# ============================================
# PART 7: STOP WITH A CUSTOM SIGNAL
# ============================================
docker stop -s SIGINT web
# ============================================
# PART 8: STOP WITH A TIMEOUT
# ============================================
docker start web
docker stop -t 30 web
# ============================================
# PART 9: RUN WITH INIT AND STOP SIGNAL
# ============================================
docker run -d --name app --init --stop-signal SIGINT --stop-timeout 20 myapp
# ============================================
# PART 10: CHECK EXIT CODE
# ============================================
docker ps -a --format "table {{.Names}}\t{{.Status}}"
# web Exited (0) 2 minutes ago
# app Exited (143) 1 minute ago
These ten parts cover restart policies, pause and unpause, restart, inspecting and changing policies, custom stop signals, timeouts, the --init flag, and reading exit codes.
Quick Reference
Lifecycle Commands
| Command | Purpose |
|---|---|
docker pause | Freeze container processes |
docker unpause | Resume frozen processes |
docker restart | Stop and start |
docker stop | Graceful shutdown |
docker kill | Immediate SIGKILL |
docker update | Change restart policy |
Restart Policies
| Policy | Restarts On |
|---|---|
no | Never |
on-failure[:n] | Non-zero exit, up to n times |
always | Any exit, including daemon restart |
unless-stopped | Any exit except explicit stop |
Signal Sequence
| Step | Signal | Effect |
|---|---|---|
| 1 | SIGTERM (default) | Request graceful shutdown |
| 2 | Grace period (10s default) | Wait for process to exit |
| 3 | SIGKILL | Force termination |
| 4 | Container stops | Exit code recorded |
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Clean exit |
| 137 | SIGKILL (128 + 9) |
| 143 | SIGTERM (128 + 15) |
| 1 | General error |
| 126 | Command not executable |
| 127 | Command not found |
Signal Flags
| Flag | Purpose |
|---|---|
--stop-signal | Signal sent by docker stop |
--stop-timeout | Grace period before SIGKILL |
docker stop -s | Override signal for one stop |
docker stop -t | Override timeout for one stop |
--init | Insert tini as PID 1 |
Best Practices
✅ Do This:
docker run -d --restart unless-stopped --name web nginx # Respect explicit stop
docker run -d --init --name app myapp # Signal forwarding
CMD ["nginx", "-g", "daemon off;"] # Exec form: app is PID 1
docker stop -t 30 db # Long timeout for DB
docker update --restart always web # Change policy
❌ Don’t Do This:
CMD /app/start.sh # ❌ Shell as PID 1
docker stop -t 0 web # ❌ No grace period
docker kill web # ❌ Skips graceful shutdown
docker run --restart always --name db postgres # ❌ Restarts even after stop
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Container takes 10s to stop | PID 1 does not handle SIGTERM | Use exec form or --init |
| Exit code 137 | Process was SIGKILLed | Handle SIGTERM; increase timeout |
| Container restarts after stop | always policy | Use unless-stopped |
| Cannot remove paused container | Processes frozen | docker unpause first |
| Data lost on restart | Process killed before flush | Handle SIGTERM; increase timeout |
| Shell script ignores signal | Shell is PID 1 | Use exec or --init |
Real-World Examples
1. Database with Long Timeout
docker run -d --name db --stop-timeout 60 postgres
2. Web Server with Restart Policy
docker run -d --name web --restart unless-stopped nginx
3. Application with Init
docker run -d --init --name app myapp
4. Custom Signal for Node.js
docker run -d --stop-signal SIGINT --name node node:20
5. Pause for Maintenance
docker pause web
# perform maintenance
docker unpause web
6. Change Policy on Running Container
docker update --restart on-failure:3 api
7. Graceful Stop with Timeout
docker stop -t 45 db
8. Immediate Kill
docker kill -s SIGKILL web
9. Restart with Custom Timeout
docker restart -t 20 web
10. Exec Form in Dockerfile
CMD ["node", "server.js"]
Visual
Signal Sequence on docker stop
┌──────────────────────────────────────────────────────────────┐
│ docker stop web │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ SIGTERM sent to PID 1 │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Process handles signal: │ │
│ │ - flush buffers │ │
│ │ - close connections │ │
│ │ - release resources │ │
│ │ - exit(0) │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ ▼ ▼ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Exits within │ │ Exits after │ │
│ │ grace period │ │ grace period │ │
│ └─────────┬──────────┘ └─────────┬──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Exit code 0 │ │ SIGKILL sent │ │
│ │ Clean shutdown │ │ Exit code 137 │ │
│ └────────────────────┘ └────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
PID 1 Signal Forwarding Problem
┌──────────────────────────────────────────────────────────────┐
│ WITHOUT exec WITH exec │
│ │
│ PID 1: /bin/sh PID 1: nginx │
│ └── PID 2: nginx └── (receives SIGTERM) │
│ │
│ SIGTERM → shell (ignored) SIGTERM → nginx (handled) │
│ nginx never notified nginx shuts down gracefully │
│ SIGKILL after 10s exits with code 0 │
│ │
│ Fix: exec nginx -g 'daemon off;' in the script │
│ Or: --init flag inserts tini as PID 1 │
└──────────────────────────────────────────────────────────────┘
Restart Policy Decision
┌──────────────────────────────────────────────────────────────┐
│ RESTART POLICY │
│ │
│ Should the container restart after a crash? │
│ │ │
│ ├── No ──▶ --restart no │
│ │ │
│ └── Yes ──▶ Should it restart after explicit stop? │
│ │ │
│ ├── No ──▶ --restart unless-stopped │
│ │ │
│ └── Yes ──▶ --restart always │
│ │
│ Retry limited times only? ──▶ --restart on-failure:N │
└──────────────────────────────────────────────────────────────┘
Pause vs Stop
┌──────────────────────────────────────────────────────────────┐
│ PAUSE STOP │
│ │
│ Processes frozen Processes terminated │
│ Memory retained Memory freed │
│ State preserved State lost (unless volume) │
│ Ports held Ports released │
│ Status: Up (Paused) Status: Exited (code) │
│ Resume: unpause Resume: start │
│ │
│ Use for temporary Use for ending the │
│ suspension. container's life. │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
docker pause | Freeze processes via cgroup freezer |
docker unpause | Resume frozen processes |
docker restart | Stop and start |
docker stop | SIGTERM, grace period, SIGKILL |
| Default grace period | 10 seconds |
| Default signal | SIGTERM (signal 15) |
| Forced signal | SIGKILL (signal 9) |
| Exit code 0 | Clean exit |
| Exit code 143 | Terminated by SIGTERM |
| Exit code 137 | Terminated by SIGKILL |
| Restart policies | no, on-failure, always, unless-stopped |
--init | Insert tini as PID 1 for signal forwarding |
--stop-signal | Custom signal for docker stop |
--stop-timeout | Custom grace period |
Key takeaways:
docker pausefreezes a container without terminating it. Memory and state are retained, anddocker unpauseresumes exactly where it stopped. A paused container holds its ports and volumes.docker stopsends SIGTERM, then SIGKILL after a grace period. The default grace period is 10 seconds, configurable with-tor--stop-timeout. The exit code reveals which signal ended the process.- PID 1 does not receive default signal handling. A container whose main process is a shell script may ignore SIGTERM, causing Docker to SIGKILL after the timeout. Use the exec form or
--initto fix this. - The
--initflag inserts tini as PID 1. It forwards signals to the child process and reaps orphaned processes, which is the recommended approach when the application cannot be PID 1 directly. - Restart policies control what happens after exit.
nonever restarts,on-failurerestarts on non-zero exit,alwaysalways restarts, andunless-stoppedrestarts except after an explicit stop. unless-stoppedis usually the right choice for production. It respects an operator’s decision to stop the container while still recovering from crashes and daemon restarts.- Custom stop signals accommodate applications that expect them.
--stop-signal SIGINTis appropriate for Node.js applications that handle SIGINT for shutdown. - Graceful shutdown requires the application to handle the signal. The container’s main process must install a handler for SIGTERM that flushes buffers, closes connections, and exits cleanly. Without it, the process is killed abruptly after the timeout.
Remember: A container’s lifecycle is not binary. Between running and removed are the states of paused, stopped, and restarting, and each transition is governed by signals and policies. docker pause freezes without losing state; docker stop requests graceful termination and escalates to force if the request is ignored. The escalation is the key mechanic: SIGTERM is a request, SIGKILL is a command, and the grace period is the window in which the application decides which one it will receive. Whether the application can use that window depends on whether its main process handles signals and whether it is PID 1. A shell script as PID 1 will ignore SIGTERM; an exec-ed application or one wrapped with --init will not. Restart policies determine what happens next: whether a crashed container is retried, whether a stopped container stays stopped, and whether a daemon restart brings containers back. When these pieces are configured correctly, containers stop cleanly, data is preserved, and services recover from failures without manual intervention.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!