One Process per Container: When the Rule Helps and When It Doesn't
AI generated
Docker · Processes · PID 1 · Supervisor · Best Practices
One Process per Container
when the rule helps, and when it doesn't

"One process per container" is one of the most frequently quoted Docker principles, and one of the most frequently misapplied. What the principle really means, where it makes sense, and when Supervisor, s6-overlay or dumb-init are the better decision.

12 min read PID 1 · Supervisor · s6-overlay · dumb-init · tini Docker Engine · Docker Compose · Production Patterns

1. Where does the one process principle come from?

The One Process per Container principle comes from the Unix philosophy: a program should do one job well and nothing beyond that. Applied to Docker, it means a container should be responsible for one clearly defined function. The idea behind it is isolation and replaceability: if the database process crashes, only the database container should restart, not also the web server that happens to run in the same container. This idea is fundamentally sound and contributes to the maintainability of container stacks.

The problem arises when the principle is interpreted literally: exactly one single operating system process is allowed to run in the container. That interpretation is neither the intent of the Docker documentation nor practically useful. PHP-FPM, for example, starts a master process and several worker processes, which is many processes, but still one concern per container: the container is responsible for running PHP code. Nginx also starts a master and several workers. That is normal and not a violation of the principle.

2. The PID 1 problem: why it matters more than the rule

Regardless of whether you strictly follow One Process per Container or not, the PID 1 problem is the actually critical topic in Docker process management. PID 1 is the first process in the container, and it has a special role in the Linux kernel: when PID 1 dies, the entire container is terminated. PID 1 is also responsible for reaping zombie processes, child processes that have terminated but whose parent has not yet executed a wait() syscall.

When a normal application runs as PID 1, it can run into trouble: many applications do not handle SIGTERM correctly when started directly as PID 1, because by default Linux does not send signals to PID 1 that would terminate it without an explicit handler. That means docker stop sends SIGTERM to PID 1, the application does not react, Docker waits 10 seconds and then sends SIGKILL, a hard kill without a graceful shutdown. A correct understanding of One Process per Container therefore has to include proper PID 1 handling.


# Demonstrate PID 1 problem: what happens without proper init
# WRONG: Application as PID 1, may not handle signals correctly
FROM php:8.4-fpm-alpine
CMD ["php-fpm"]
# php-fpm receives SIGTERM as PID 1, but handles it correctly (has signal handlers)

# WRONG: Shell as PID 1, shell does not forward signals to children
FROM php:8.4-fpm-alpine
CMD ["/bin/sh", "-c", "php-fpm -F"]
# docker stop → SIGTERM to /bin/sh → sh ignores it → 10s timeout → SIGKILL

# RIGHT: Use exec to replace shell with the actual process
FROM php:8.4-fpm-alpine
CMD ["php-fpm", "-F"]  # exec form, PID 1 is php-fpm directly

# RIGHT: Use dumb-init as a minimal init system
FROM php:8.4-fpm-alpine
RUN apk add --no-cache dumb-init
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["php-fpm", "-F"]
# dumb-init as PID 1: forwards signals, reaps zombies, proper shutdown

3. What the principle actually means

Correctly interpreted, the One Process per Container principle means: a container has a single responsibility (Single Responsibility). A container is responsible for running PHP-FPM, and only for that. It is not simultaneously responsible for running Nginx, Redis and a cron job. This separation of responsibilities enables independent scaling (more PHP-FPM containers without scaling Nginx), independent updates and clear restart policies.

That is something different from saying a container may only ever have a single operating system process. PHP-FPM with a master and workers is one concern: running PHP. Nginx with a master and workers is one concern: serving HTTP. If Supervisor manages both PHP-FPM and a queue worker in one container, and both conceptually belong to the same application layer and must be deployed together, that is a pragmatic and acceptable decision for many teams, as long as the boundaries are clearly documented.

4. When one process per container is the right choice

The One Process per Container principle delivers the most value when services need to be scaled, updated or restarted independently. A high-traffic Magento installation might need ten PHP-FPM containers but only one Nginx container, and that is only possible if both are separate services. A queue worker that consumes heavy CPU should be able to scale without also increasing the number of PHP-FPM workers.

Also a strong argument for One Process per Container: differing process lifetimes. A cron job runs briefly and ends, a web server runs continuously. If both run in the same container, the container dies as soon as the cron job finishes, which complicates restart policies. Separate containers with their own restart policies (restart: unless-stopped for the web server, restart: no for the one-off cron job) are the clean solution.

5. Where the principle goes too far

The One Process per Container principle is taken too far when it leads to a proliferation of containers that actually always have to be deployed, scaled and restarted together. A PHP application with a tightly coupled background worker that shares the same code, accesses the same shared memory region and must always be deployed in the same version is a candidate for a single container with two processes, managed by a lightweight process manager.

Another typical scenario where One Process per Container goes too far: combining the application with a health check process, log forwarder or metrics collector. These sidecar processes are conceptually bound to the main application and often cause more overhead than benefit when moved into their own containers. Kubernetes has the sidecar container concept for this; in Docker Compose, a minimal init system with several closely related processes is often the more pragmatic solution.


# supervisord.conf: manage multiple related processes in one container
[supervisord]
nodaemon=true    ; Keep supervisor as PID 1 in the foreground
user=root
logfile=/dev/null
pidfile=/tmp/supervisord.pid

[program:php-fpm]
command=/usr/local/sbin/php-fpm -F
autostart=true
autorestart=true
priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:queue-worker]
command=/usr/local/bin/php /var/www/html/bin/magento queue:consumers:start async.operations.all --single-thread
autostart=true
autorestart=true
priority=20
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

# Dockerfile: use supervisor as the container entrypoint
# FROM php:8.4-fpm-alpine
# RUN apk add --no-cache supervisor
# COPY supervisord.conf /etc/supervisord.conf
# CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

6. Supervisor and s6-overlay: managing multiple processes cleanly

When several processes need to run in one container, Supervisor is a proven tool. Supervisor runs as PID 1 in the foreground (nodaemon=true), monitors all configured processes, restarts them on crashes, and forwards logs to stdout/stderr correctly. Supervisor's SIGTERM handler ensures an orderly graceful shutdown of all managed processes on docker stop. That solves the PID 1 problem and the zombie reaping problem at the same time.

s6-overlay is a more modern alternative to Supervisor for Docker containers, built on the s6 init system. It is leaner than Supervisor and especially common in community base images such as the linuxserver.io images. s6-overlay cleanly separates init phases, service definitions and shutdown sequences, and is more flexible for conditional service starts. For simple scenarios, Supervisor is sufficient; for complex init logic with multiple dependencies and startup ordering, s6-overlay is the stronger choice, provided you are willing to get to grips with its configuration structure.

7. dumb-init and tini: minimal init systems for Docker

For scenarios where genuinely only a single main process runs in the container, but the PID 1 problem still needs to be addressed, dumb-init and tini are the lightweight solution. dumb-init is a minimal init system that is started as PID 1, forwards signals correctly to its child process, and reaps zombie processes. It has no service management functions at all; it is purely a PID 1 wrapper.

tini is functionally similar to dumb-init and has been built into the Docker engine as the --init flag since Docker 1.13: docker run --init my-image starts tini automatically as PID 1. In Docker Compose, init: true can be set at the service level. For most One Process per Container scenarios, that is enough: the application runs correctly, signals get forwarded, zombies get cleaned up, all without Supervisor or s6-overlay having to go into the image.

8. Signal handling and graceful shutdown

Correct signal handling is the most important property a container process must have, more important than the question of whether there is exactly one process or several. On docker stop, Docker sends SIGTERM to PID 1 and waits ten seconds by default. If the process does not react, SIGKILL follows. For Magento, a hard kill in the middle of an active database connection means potential data corruption or corrupted cache structures.

PHP-FPM understands SIGTERM and shuts down workers cleanly after finishing in-flight requests. Nginx does too. The problem arises when shell scripts act as the CMD entry: the shell does not forward SIGTERM to its child processes. The solution is consistently using the exec form for CMD (["php-fpm", "-F"] instead of sh -c "php-fpm -F"). Alternatively, the STOPSIGNAL Dockerfile directive can be set to a signal the application handles reliably.

9. Process management approaches compared

Choosing the right process management approach depends on the number of processes, the complexity of the init logic, and the team's know-how. The table below shows the main options for Docker containers.

Approach PID 1 safe Multi-process Complexity
Direct process (exec form) Depends on the app No Minimal
dumb-init / tini Yes No (wrapper only) Very low
docker run --init Yes No No image change
Supervisor Yes Yes Medium
s6-overlay Yes Yes High

For most Magento containers with PHP-FPM, nginx and Redis as separate services, the combination of the direct exec form and init: true in Compose is sufficient. Supervisor becomes worthwhile when tightly coupled worker processes need to run alongside the main service. s6-overlay comes into play for complex init ordering and conditional service starts.

Mironsoft

Container architecture, process management and production hardening

Need your container processes structured correctly?

We analyze your container architecture for PID 1 problems, missing signal handlers and suboptimal process structures, and implement the right solution, from dumb-init to Supervisor.

PID 1 audit

Analyze and secure containers for signal handling and zombie reaping

Graceful shutdown

docker stop without SIGKILL: configure STOPSIGNAL, exec form and shutdown timeouts correctly

Process architecture

Supervisor or s6-overlay setup for multi-process containers in production

10. Summary

The One Process per Container principle is a valuable design goal, but not an absolute law. Correctly understood, it means: a container has a single responsibility and can be scaled, updated and restarted independently. The more important technical problem, the PID 1 problem with missing signal handling and zombie reaping, is orthogonal to the question of process count and must be addressed in every container.

For simple cases, the exec form for CMD together with init: true in Docker Compose is enough. For tightly coupled processes, Supervisor with nodaemon=true is the pragmatic solution. s6-overlay is recommended for complex init scenarios. The most important thing: containers should shut down cleanly on docker stop. Graceful shutdown requires PID 1 to understand and forward SIGTERM. Anyone who ensures that has fulfilled the most important property of a production-ready container.

One Process per Container: the essentials at a glance

The real principle

One concern per container, not literally one operating system process. PHP-FPM with a master and workers is one responsibility.

PID 1 problem

A shell as PID 1 does not forward SIGTERM. Use the exec form for CMD, or dumb-init/tini as a minimal init system.

Graceful shutdown

init: true in Compose or dumb-init in the Dockerfile. Set STOPSIGNAL correctly. docker stop waits 10s, then SIGKILL.

Multi-process

Supervisor with nodaemon=true for tightly coupled processes. s6-overlay for complex init ordering. Both solve PID 1 and zombie reaping.

11. FAQ: One Process per Container

1Does One Process per Container really mean a single OS process?
No. One responsibility per container. PHP-FPM with workers is one responsibility. The principle is aimed at containers that run a web server, DB and worker at the same time.
2What is the PID 1 problem in Docker?
PID 1 receives signals differently than other processes. Without a SIGTERM handler: docker stop waits 10s, then SIGKILL. No chance for a graceful shutdown.
3What does dumb-init do?
Minimal init as PID 1. Forwards signals, reaps zombies. No service management. Sufficient for single process containers.
4dumb-init vs. tini?
tini is built into Docker: init: true in Compose, no image change needed. dumb-init has to go into the image but allows more targeted configuration.
5When to use Supervisor in Docker?
When several tightly coupled processes always need to be deployed together. PHP-FPM plus a queue worker of the same codebase is a good candidate for Supervisor.
6Why is a shell as CMD a problem?
The shell does not forward SIGTERM. The application gets no signal, no graceful shutdown. Exec form or exec inside shell scripts is the fix.
7Supervisor vs. s6-overlay?
Supervisor: simpler ini configuration, well documented. s6-overlay: leaner, complex init ordering, steeper learning curve. For simple cases, Supervisor.
8How long does docker stop wait?
10 seconds by default. stop_grace_period: 30s in Compose or -t 30 on the command increases the timeout. For PHP-FPM with long requests, 30 to 60s is recommended.
9What is STOPSIGNAL in the Dockerfile?
Defines the signal used on docker stop. Default: SIGTERM. Nginx reacts faster to SIGQUIT. STOPSIGNAL SIGQUIT in the Dockerfile changes the behavior image wide.
10Two independent services in one container?
Technically possible, architecturally not recommended. Independent scaling and updating is no longer possible. It undermines all the benefits of container orchestration.