How to Use the Init Process Flag and Custom Init Images in Container
Use the --init flag to run the built-in vminitd init process as PID 1 for signal forwarding and zombie reaping, or specify --init-image <image> to provide a custom init image that replaces the default binary for specialized boot-time logic.
The Apple Container project provides mechanisms to handle PID 1 responsibilities in lightweight VMs. When containers run without proper init handling, user applications may ignore termination signals or leave zombie processes behind. The --init and --init-image flags solve this by injecting proper init management into the container lifecycle according to the source code in apple/container.
Understanding the Init Process in Containers
Why PID 1 Matters
When a container starts, the specified command becomes PID 1 inside the VM. This process is responsible for reaping orphaned child processes and forwarding termination signals like SIGTERM and SIGINT to the running workload. Most user-space applications are not designed to handle these responsibilities, leading to resource leaks and unresponsive containers.
The Built-in Init Solution
The Container CLI provides a lightweight built-in init process called vminitd. When enabled, this binary runs as PID 1, starts the container's main process as a child, and handles signal forwarding and process reaping via waitpid() loops.
Using the --init Flag for Signal Management
The --init flag is defined in Sources/Services/ContainerAPIService/Client/Flags.swift at lines 61-63:
@Flag(name: .customLong("init"),
help: "Run an init process inside the container that forwards signals and reaps processes")
public var useInit = false
When you pass this flag, the runtime creates a VM where vminitd runs as the first process. This ensures that signals from the host are properly forwarded to your application and that orphaned child processes are cleaned up automatically.
Run a container with the built-in init process:
container run --init ubuntu:latest my-app
The command my-app runs as a child of the lightweight init, which will reap any orphaned processes and forward signals appropriately.
You can also use the flag with container create for later management:
container create --init --name my-container ubuntu:latest my-app
container start my-container
Creating and Using Custom Init Images
The --init-image flag allows you to replace the default vminitd binary with a custom image containing specialized boot-time logic. This is declared in Sources/Services/ContainerAPIService/Client/Flags.swift at lines 64-68:
@Option(name: .long,
help: .init("Use a custom init image instead of the default", valueName: "image"))
public var initImage: String?
Custom init images run inside the same lightweight VM before the OCI container starts, enabling you to inject helper daemons, install eBPF filters, or print diagnostic messages.
Building a Custom Init Wrapper
Below is a minimal Go wrapper that logs a diagnostic message to the kernel log and then hands off to the real vminitd binary (typically renamed vminitd.real):
// wrapper.go
package main
import (
"os"
"syscall"
)
func main() {
// Write a diagnostic line to the kernel log
if kmsg, err := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0); err == nil {
kmsg.WriteString("<6>custom-init: === CUSTOM INIT IMAGE RUNNING ===\n")
kmsg.Close()
}
// Exec the original vminitd binary
if err := syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ()); err != nil {
os.Exit(1)
}
}
Build the wrapper for the VM's architecture:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go
Create a Dockerfile that replaces the default vminitd with your wrapper:
# Use the same vminit base as the upstream project
FROM ghcr.io/apple/containerization/vminit:0.33.3 AS base
FROM ghcr.io/apple/containerization/vminit:0.33.3
COPY --from=base /sbin/vminitd /sbin/vminitd.real # keep the original as .real
COPY wrapper /sbin/vminitd # replace with our wrapper
Build the custom init image:
container build -t local/custom-init:latest .
Running with --init-image
Execute a container using your custom init image:
container run --name my-container \
--init-image local/custom-init:latest \
alpine:latest echo "hello world"
Verifying Custom Init Execution
To confirm that your custom init image ran successfully, check the boot logs:
container logs --boot my-container | grep custom-init
Expected output:
[ 0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===
This verification method uses the container logs --boot command to access kernel-level messages from the VM initialization phase.
Summary
- The
--initflag runs the built-invminitdbinary as PID 1 to handle signal forwarding and zombie process reaping automatically. - The
--init-image <image>flag allows you to specify a custom init image that replaces the defaultvminitdbinary for specialized boot-time behavior. - Custom init images typically wrap the original
vminitdbinary (asvminitd.real) to add diagnostic logging, helper daemons, or other pre-container initialization. - Both flags are defined in
Sources/Services/ContainerAPIService/Client/Flags.swiftand documented indocs/command-reference.mdanddocs/how-to.md. - Verify custom init execution using
container logs --bootto check kernel messages from the VM initialization phase.
Frequently Asked Questions
What is the difference between --init and --init-image?
The --init flag enables the default built-in init process (vminitd) that handles signal forwarding and zombie reaping. The --init-image flag specifies a custom OCI image to use instead of the default init binary, allowing you to inject custom boot-time logic while still maintaining init responsibilities. When using --init-image, you typically still need the underlying init functionality, so custom images often wrap and then exec the original vminitd binary.
How do I build a custom init image for the Container CLI?
Build a binary that performs your custom initialization (such as logging to /dev/kmsg or setting up eBPF filters), then execs /sbin/vminitd.real to hand off to the standard init process. Package this binary in a Docker image based on ghcr.io/apple/containerization/vminit, replacing /sbin/vminitd with your wrapper while keeping the original as vminitd.real. Build for the target architecture (typically arm64 or amd64) with CGO_ENABLED=0.
Can I use custom init images with container create?
Yes, the --init-image flag works with both container run and container create commands. When used with container create, the custom init image is configured in the container metadata and used when the container subsequently starts via container start.
Where are the init flags defined in the source code?
The --init and --init-image flags are declared in Sources/Services/ContainerAPIService/Client/Flags.swift at lines 61-68. The --init flag uses the @Flag property wrapper with a custom long name, while --init-image uses the @Option property wrapper. User-level documentation appears in docs/how-to.md (lines 41-55) and docs/command-reference.md (lines 56-58).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →