A minimalist process supervisor and init system written in the Rust Programming Language. It is specifically designed to act as a lightweight PID 1 process in containerized environments, ensuring that zombie processes are reaped and system signals are handled gracefully.
Note
Ocelot is designed for specific use cases like container initialization and minimal supervision scenarios. It is not intended to replace comprehensive init systems like systemd in general-purpose operating systems.
graph TD
subgraph Host_OS ["Host OS / Container Runtime (Docker/K8s)"]
Signal["Send Signal<br/>(e.g., SIGTERM)"]
end
subgraph Container ["Container (PID Namespace)"]
Ocelot["<b>Ocelot (PID 1)</b><br/>(Init System / Daemon)"]
subgraph Managed_Processes ["Managed Processes"]
App["<b>Main Application</b><br/>(Child Process)"]
Orphan["Orphan Processes"]
end
end
%% Signal Flow
Signal -->|1. Intercept Signal| Ocelot
Ocelot -->|2. Signal Forwarding| App
%% Process Management Flow
App -.->|3. Terminate| Ocelot
Orphan -.->|4. Re-parenting| Ocelot
Ocelot -->|5. Reaping| Orphan
%% Styles
style Ocelot stroke:#333,stroke-width:2px
style App stroke:#333
style Orphan stroke:#999,stroke-dasharray: 5 5
- Quick Start
- Which Command Should I Use?
- FAQ
- Concept
- Command Line Interface
- Resource Usage
- Installation
- Running in Docker
- Configuration Reference
- Examples
- Contributing
- License
First, copy Ocelot from the container registry into your Dockerfile:
COPY --from=ghcr.io/xrelkd/ocelot:latest /usr/bin/ocelot /usr/bin/ocelot# Single app with signal forwarding
docker run -it --entrypoint ocelot your-image entry -- your-app
# Multiple processes with health probes
docker run -it --entrypoint "ocelot supervise run --file /etc/ocelot/supervisor.yaml" your-imageFirst time? Start with
entryfor simple apps, orsupervisefor complex multi-process workloads.
| Command | Use Case | Example |
|---|---|---|
idle |
Just reap zombies and hold namespaces (Kubernetes pause equivalent) | Pod infrastructure container |
entry |
Run one main app with signal forwarding and zombie reaping | Single-process containers |
supervise |
Multiple processes with health probes, restart policies, dependencies | Complex microservices |
bootstrap |
VM boot / initramfs initialization | QEMU VMs, embedded systems |
zombie |
Testing/debugging zombie process handling | Local testing only |
Do you need to run applications?
ββ No β Use `idle` (just hold namespaces)
ββ Yes β How many?
ββ One β Does it need health checks / restart?
β ββ No β Use `entry`
β ββ Yes β Use `supervise`
ββ Many β Use `supervise`
Need to boot a VM?
ββ Use `bootstrap`
If your container runs only a single process and your application handles signals properly, you may not need Ocelot. However, Ocelot is recommended when:
- You run multiple processes (e.g., sidecar containers, log collectors)
- You want built-in zombie reaping without modifying your application
- You need health checks and restart policies
Ocelot compiles to a static binary of approximately 2-3 MB, making it lightweight for container deployments. You can further reduce size with --release and stripping debug symbols.
Yes! Ocelot works seamlessly with Kubernetes:
- Use
idleas a pause container replacement - Use
entryorsuperviseas your container's entrypoint - Works with liveness/readiness probes (when using
supervise)
Not currently. Ocelot is designed for Linux containers and VMs.
The idle command is the core functionality for container init responsibilities. It is designed to be a direct replacement for the Kubernetes pause process, serving as the "infra" container or parent process that:
- Holds Namespaces: Keeps the network/IPC namespaces alive by waiting indefinitely.
- Reaps Zombies: Acts as
PID 1to listen forSIGCHLDand reap orphaned processes. - Graceful Shutdown: Properly handles
SIGINTorSIGTERMto allow the pod to terminate cleanly.
The entry command provides a robust entry point for containerized workloads, serving as a minimal init system (PID 1). It is designed to manage the full lifecycle of a primary application while ensuring the container remains stable and responsive. Its key responsibilities include:
- Process Supervision: Spawns a child process via fork/exec and tracks its execution state, returning the correct Unix exit codes (including signal offsets).
- Signal Forwarding & Proxying: Intercepts
SIGINTandSIGTERMfrom the container runtime and propagates them to the child process to facilitate graceful shutdowns. - Zombie Reaping: Monitors
SIGCHLDto proactively reap orphaned or "zombie" processes, preventing process table exhaustion within the PID namespace. - Graceful Timeout Enforcement: Implements a configurable "kill-timer" that allows the child process a window to exit cleanly before forcibly terminating it with SIGKILL.
The supervise command is an advanced multi-process supervisor designed for managing complex containerized workloads. It provides enterprise-grade process management features including:
- Multi-Process Management: Spawn and manage multiple processes defined in a YAML configuration file
- Health Probes: Built-in readiness and liveness probe support with multiple handler types:
- HTTP GET probes for HTTP endpoints
- TCP Socket probes for TCP port checks
- Restart Policies: Flexible restart strategies:
Never: Do not restart on exitOnFailure: Restart on non-zero exit (with configurable max retries and backoff)Always: Always restart on exit
- Graceful Shutdown: Configurable termination signals (
SIGTERM,SIGKILL, etc.) and grace periods - Dependency Management: Orchestrates process startup and shutdown ordering through the orchestrator
- Process State Tracking: Monitors and tracks the state of each managed process
- Configuration Validation: Validate configuration files without starting the supervisor using
ocelot supervise validate <config-file>. Supports JSON output with--output jsonfor automation.
The bootstrap command acts as an initramfs init system for QEMU VMs. It provides a three-tier phased initialization architecture:
- Pre-switch Phase: Mounts virtual filesystems, loads kernel modules, configures system settings
- Switch-root Phase: Mounts the root filesystem and performs
switch_root - Post-switch Phase: Continues configuration and hands off to
superviseorchestrator,shell, orexecprogram
Key features include:
- Kernel modules loading
- Virtual filesystem mounting (
procfs,sysfs,devpts,tmpfs, etc.) - Root filesystem mounting (device,
virtiofs,9p,NFS,overlay) - Boot script execution
- Handoff modes: supervise orchestrator, interactive shell, or exec program
The zombie command is a specialized systems utility that illustrates a classic edge case in Unix process management.
[!WARNING] This command is intended for local testing and educational use. Generating an excessive number of zombie processes can exhaust the system's process ID (PID) limit, potentially preventing new processes from starting.
Upon execution, the program enters a continuous loop where it utilizes the fork() system call to spawn new child processes. Each child process is programmed to terminate immediately. However, the parent process is explicitly designed to not call wait() or waitpid().
Under standard Unix semantics, when a child terminates, the kernel retains its exit status and process ID in the process table so the parent can eventually retrieve it. Because this parent process ignores these "death certificates," the children transition into a Zombie state (Z), appearing as <defunct> in system monitors like ps or top.
The application is built to be "fire-and-forget":
- Signal Interruption: The parent process monitors for
SIGINT(Ctrl+C) andSIGTERM. - Instant Exit: Upon receiving these signals, the parent terminates immediately without attempting to clean up or "reap" its children.
- System Recovery: Once the parent process dies, the orphaned zombie processes are adopted by the system's init process (PID 1), which automatically reaps them, clearing them from the system process table.
| Variable | Description | Default |
|---|---|---|
OCELOT_LOG_LEVEL |
Set the logging level (trace, debug, info, warn, error) | info |
# Show version
$ ocelot --version
# Show usage
$ ocelot --help
Process supervisor and init system written in Rust Programming Language
Usage: ocelot [COMMAND]
Commands:
version Print the version information
completions Output shell completion code for the specified shell (bash, zsh, fish)
idle Run as a minimalist PID 1 to reap zombies and hold namespaces [aliases: noop, pause]
entry Spawns and supervises a child process as a minimalist PID 1 with signal forwarding and zombie reaping [aliases: wrap]
supervise Run supervisor with configuration file
bootstrap Run as initramfs init - mount rootfs and exec supervise [aliases: boot]
zombie Creates zombie processes by forking child processes that immediately exit, while the parent process sleeps. This is useful for testing how systems handle zombie processes.
zombie-finder Scan system for zombie processes
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
# Show usage
$ ocelot idle --help
# Run
$ ocelot idle
# Show usage
$ ocelot entry --help
# Run with a child process
$ ocelot entry sleep 10
# Show usage
$ ocelot supervise --help
# Generate the configuration file
$ ocelot supervise config-template > /path/to/the/configuration/file
# Open your favorite editor and edit the configuration file
$ vim /path/to/the/configuration/file
# Validate the configuration file
$ ocelot supervise validate /path/to/the/configuration/file
# Run supervisor with configuration file
$ ocelot supervise run --file /path/to/the/configuration/file
[!Note] For configuration details, see the Configuration Reference section.
# Show usage
$ ocelot bootstrap --help
# Generate the configuration file
$ ocelot bootstrap config-template > /path/to/the/configuration/file
# Open your favorite editor and edit the configuration file
$ vim /path/to/the/configuration/file
# Validate the configuration file
$ ocelot bootstrap validate /path/to/the/configuration/file
[!Note] For configuration details, see the Configuration Reference section.
[!Note] For examples of
initramfssetup and QEMU VM booting, see the Examples section.
# Show usage
$ ocelot zombie --help
# Generate zombies
$ ocelot zombie
# Show usage
$ ocelot zombie-finder --help
# List zombies
$ ocelot zombie-finder
| Metric | Value |
|---|---|
| Binary size (release, musl) | ~2-3 MB |
| Static binary | Yes (no glibc dependency) |
| Memory usage | ~1-2 MB idle |
| Dependencies | None (fully static) |
Build with cargo build --release --target x86_64-unknown-linux-musl for fully static binaries.
Ocelot is designed for minimal resource overhead, making it ideal for containerized environments where every megabyte counts.
To build and install Ocelot from source, ensure you have the Rust toolchain installed:
git clone https://github.com/xrelkd/ocelot.git
cd ocelot
cargo install --path .To build a fully static-linked binary (no glibc dependency) for container use:
cargo build --release --target x86_64-unknown-linux-muslThe static binary will be at target/x86_64-unknown-linux-musl/release/ocelot.
Generate autocompletion scripts for your favorite shell:
# For Zsh
ocelot completions zsh > /usr/local/share/zsh/site-functions/_ocelot
# For Bash
ocelot completions bash > /etc/bash_completion.d/ocelotUsing Ocelot as your ENTRYPOINT ensures that your container correctly manages the process lifecycle.
- Use the "idle" command for a simple init system that holds namespaces and reaps zombies
# Use ocelot as the init system in your Dockerfile
COPY --from=ghcr.io/xrelkd/ocelot:latest /usr/bin/ocelot /usr/bin/ocelot
# Run with 'idle' to handle PID 1 duties
ENTRYPOINT ["ocelot", "idle"]- Use the "entry" command to supervise a child process with signal forwarding and zombie reaping
# Use ocelot as the init system in your Dockerfile
COPY --from=ghcr.io/xrelkd/ocelot:latest /usr/bin/ocelot /usr/bin/ocelot
# Run with 'entry' to handle PID 1 duties
ENTRYPOINT ["ocelot", "entry", "--", "ocelot", "zombie", "--count=20"]- Use the "supervise run" command to manage multiple processes with health probes and restart policies
# Use ocelot as the init system in your Dockerfile
COPY --from=ghcr.io/xrelkd/ocelot:latest /usr/bin/ocelot /usr/bin/ocelot
COPY supervisor.yaml /etc/ocelot/supervisor.yaml
# Run with 'supervise run' to manage multiple processes
ENTRYPOINT ["ocelot", "supervise", "run", "--file", "/etc/ocelot/supervisor.yaml"]Container doesn't stop gracefully?
- Check your application's signal handling
- Verify your app receives
SIGTERM(add debug logging)
Zombie processes appearing?
- Ensure Ocelot is PID 1
- Check child processes aren't double-forking
Ocelot uses two different configuration systems depending on the command:
- Supervise: For managing multiple processes in containerized workloads (PID 1 replacement with health probes, restart policies, and dependency management)
- Bootstrap: For initramfs-style system initialization (mounting filesystems, loading modules, switch_root, and handoff to supervisor or shell)
For full configuration documentation, see:
See the examples directory for practical demonstrations of Ocelot usage, including QEMU virtual machine initialization.
Contributions are welcome! Before you start, please read:
- Contributing Guide β Development workflow, git conventions, commit message format, and PR process
- Coding Conventions β Rust coding standards covering imports, attributes, error handling, async patterns, and testing
Quick start:
# Enter the development environment (requires Nix)
nix develop
# Or use direnv for automatic environment loading
direnv allow
# Build and test
cargo build
cargo nextest runOcelot is licensed under the GNU General Public License version 3. See LICENSE for more information.