Lee Cheng Hui

How to reap zombie processes in Docker containers

tldr: Use tini to handle zombie processes in Docker containers.

I have a containerized Python FastAPI service that spawns child Chrome processes to fetch web content. I deployed it on my VPS and used it for quite some time. One day, when I logged into my machine, I saw the following:

Welcome to Ubuntu 22.04.5 LTS

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

  => There are 16 zombie processes.

It was my first time seeing this message. When I looked at the running processes, I saw some processes had the <defunct> at the end:

username@hostname:~# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root     2245731 2243994  0 21:16 ?        00:00:00 [chrome_crashpad] <defunct>
root     2245733 2243994  0 21:16 ?        00:00:00 [chrome_crashpad] <defunct>
root     2245736 2243994  0 21:16 ?        00:00:00 [chrome] <defunct>
root     2245737 2243994  0 21:16 ?        00:00:00 [chrome] <defunct>

A defunct process is a process that has completed its task but still has an entry in the processes table. It’s the parent process’s responsibility to read the child process’s exit status and clear the process table. However, in my case, the uvicorn process in my Docker container doesn’t automatically reap orphaned processes.

Here is my Dockerfile for reference, at which the unicorn is the PID 1 of my image:

# Runtime stage
FROM python:3.14-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97

# # Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    wget \
    libxml2 \
    libxslt1.1 \
    zlib1g \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean
# Copy virtual environment from builder
...

# Copy application code
COPY app /app
ENV PYTHONPATH=/app

EXPOSE 8000

# uvicorn is the PID 1
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

To solve this orphan processes issue, I found tini which reap orphan processes automatically, and I just need to modify two lines of my Dockerfile:

FROM python:3.14-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    wget \
    libxml2 \
    libxslt1.1 \
    zlib1g \
    tini \ # install tini
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean


COPY app /app
ENV PYTHONPATH=/app

EXPOSE 8000

# tini is the new PID 1
ENTRYPOINT ["tini", "--"] # add a new entrypoint
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

With tini installed, I no longer see zombie processes.

As a side note, tini is included in Docker version 1.13+. So, without touching the Dockerfile, I can achieve the same thing by either passing the --init flag in Docker run, or in Docker Compose:

services:
  myservice:
    image: alpine:latest
    init: true # add this, source: https://docs.docker.com/reference/compose-file/services/#init