How to Use Custom Init Images for Boot Customization in Apple's Container Runtime
Use the --init-image <image> flag to replace the default vminitd binary with a custom wrapper that executes arbitrary boot-time logic before handing control to the real init process.
Apple's container runtime executes each container inside a lightweight VM that boots with an init filesystem image containing the vminitd binary. By leveraging custom init images, you can inject specialized initialization logic—such as logging, eBPF filter loading, or auxiliary daemon startup—into the VM boot sequence before the OCI container image starts.
Understanding the Init Image Architecture
The container runtime launches containers within lightweight virtual machines. Each VM boots with an init filesystem image that provides the vminitd binary, which serves as the container's init process. By default, this image is defined in the runtime configuration under the [vminit] section, typically pointing to ghcr.io/apple/containerization/vminit:0.34.0.
When you specify a custom init image, the runtime fetches and mounts your filesystem in place of the default. The VM then executes your custom binary as PID 1, allowing you to instrument the very first code that runs before any user-provided container workload begins.
Creating a Custom Init Image
A custom init image is a small Linux filesystem containing a wrapper binary that replaces the original vminitd. The wrapper performs your custom boot-time work and then transfers control to the real vminitd via exec.
Writing the Wrapper Binary
The wrapper must handle the transition to the real init system. Below is a minimal Go implementation that logs a message to the kernel ring buffer before handing execution to the original binary.
// wrapper.go – a minimal Go wrapper for vminitd
package main
import (
"os"
"syscall"
)
func main() {
// Write a custom message to the kernel log
kmsg, err := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0)
if err == nil {
kmsg.WriteString("<6>custom-init: === CUSTOM INIT IMAGE RUNNING ===\n")
kmsg.Close()
}
// Hand over to the real vminitd binary
err = syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ())
if err != nil {
os.Exit(1)
}
}
Build the wrapper for the target architecture (typically arm64 for Apple Silicon):
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go
Building the Container Image
Create a Dockerfile that uses the same vminit base image as the runtime to ensure kernel compatibility. The Dockerfile preserves the original vminitd as vminitd.real and installs your wrapper in its place.
# Dockerfile that creates the custom init image
FROM ghcr.io/apple/containerization/vminit:0.34.0 AS base
FROM ghcr.io/apple/containerization/vminit:0.34.0
# Keep the original binary as vminitd.real
COPY --from=base /sbin/vminitd /sbin/vminitd.real
# Replace it with our wrapper
COPY wrapper /sbin/vminitd
Build the image using the container CLI:
container build -t local/custom-init:latest .
Deploying with the --init-image Flag
The --init-image flag overrides the default init image for a single run or create operation. In Sources/Services/ContainerAPIService/Client/Flags.swift, the property initImage captures the flag value and propagates it through the command-line parsing layer. This value is forwarded via Sources/ContainerCommands/Container/ContainerRun.swift to the runtime service.
In Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift, the runtime fetches the specified image and mounts it as the init filesystem before starting the OCI container.
Run a container with your custom init image:
container run --name my-container \
--init-image local/custom-init:latest \
alpine:latest echo "hello"
Verifying Boot-Time Execution
To confirm your custom init logic executed during the VM boot sequence, inspect the VM boot logs using the --boot flag. The wrapper's early execution means its output appears in the kernel log before container runtime logs begin.
container logs --boot my-container | grep custom-init
Expected output:
[ 0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===
Summary
- Custom init images replace the default
vminitdbinary in Apple's container runtime, enabling boot-time customization inside the lightweight VM. - The
--init-image <image>flag routes throughFlags.swiftandContainerRun.swifttoContainersService.swift, which mounts your filesystem before container startup. - Implementation requires a wrapper binary that performs custom work and
execs the original/sbin/vminitd.real, packaged in a container image based on the officialvminitbase. - Verification uses
container logs --boot <name>to inspect kernel messages written before the container workload begins.
Frequently Asked Questions
What is the default init image used by Apple's container runtime?
The default init image is defined in the runtime configuration under the [vminit] section, typically referencing ghcr.io/apple/containerization/vminit:0.34.0. This image contains the vminitd binary that serves as the VM's init process.
Can I use any container image as a custom init image?
No. A valid custom init image must contain a compatible vminitd binary (or wrapper) that can function as PID 1 within the VM. Apple recommends basing your image on the same vminit version that ships with the runtime to ensure compatibility with the VM kernel and versioning constraints.
How do I troubleshoot a custom init image that fails to boot?
Check the VM boot logs using container logs --boot <container-name> to see if your wrapper produced any output before failing. Ensure your wrapper correctly execs /sbin/vminitd.real with the original arguments and environment. Verify that the wrapper binary is compiled for the correct architecture (usually arm64) and has executable permissions.
Is the --init-image flag available for container create operations?
Yes. The --init-image flag works for both container run and container create operations. In Sources/Services/ContainerAPIService/Client/Flags.swift, the initImage property is parsed and passed to the runtime service regardless of whether you are running or creating a container.
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 →