How to Use the Custom Init Image Feature in Container
The custom init image feature allows you to replace the default vminitd bootstrap filesystem with your own image, enabling pre-boot custom logic, debugging, and VM-level services before the OCI container's PID 1 process starts.
Container, Apple's open-source containerization framework, runs OCI containers inside lightweight VMs using a minimal init process called vminitd to prepare the environment. By default, the VM boots with a standard vminit image defined in ContainerSystemConfig.swift, but the custom init image feature lets you supply your own init filesystem via the --init-image flag. This capability allows you to execute arbitrary code, configure hardware, or start background services before the container's main process takes over.
What Is the Custom Init Image?
In the Container architecture, every VM boots with a dedicated init filesystem containing the vminitd binary. According to the source code in Sources/ContainerPersistence/ContainerSystemConfig.swift, this default vminit image handles initial setup before handing control to the container's entrypoint.
The custom init image feature overrides this default by mounting your specified image as the VM's root filesystem. Your custom image must contain a vminitd binary—either a modified version or a wrapper—that executes your custom logic before starting the container's actual init process. As implemented in Sources/ContainerCommands/System/SystemStart.swift, the system calls installInitialFilesystem(initImage:) to mount your custom image during VM startup.
When You Need a Custom Init Image
Use a custom init image when you require logic to run before the container's PID 1 starts, without modifying the container image itself. Common scenarios include:
- Boot-time custom logic: Run initialization scripts, configure kernel parameters, or mount additional volumes before the OCI container starts.
- VM-level daemons: Start eBPF network filters, logging agents, or monitoring services that must live outside the container's namespace and persist for the VM's lifetime.
- Debugging and instrumentation: Insert kernel log messages, pause the boot sequence for inspection, or replace
vminitdwith a test binary to troubleshoot VM startup issues. - Security hardening: Pre-load security modules, SELinux policies, or integrity checks that must be present before the container runtime initializes.
Building and Using a Custom Init Image
Creating a custom init image involves building a wrapper binary, packaging it into a container image, and referencing it at runtime. The following steps mirror the implementation documented in docs/how-to.md.
Step 1: Create a Wrapper Binary
Create a Go wrapper that performs custom work before executing the real vminitd. This example writes a message to the kernel log and then hands off to the original binary:
// wrapper.go
package main
import (
"os"
"syscall"
)
func main() {
// Write a message 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()
}
// Hand off to the real init binary
if err := syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ()); err != nil {
os.Exit(1)
}
}
Step 2: Build the Wrapper Binary
Compile the wrapper for the target VM architecture (typically ARM64 for Apple Silicon):
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go
Step 3: Create the Init Image Containerfile
Write a Containerfile that copies the real vminitd to a backup location and installs your wrapper as the new init binary:
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
COPY wrapper /sbin/vminitd
Build the custom init image using the Container CLI:
container build -t local/custom-init:latest .
Step 4: Run with the Custom Init Image
Specify your custom init image using the --init-image flag when running or creating a container. As documented in docs/command-reference.md, this flag tells the runtime to use your image instead of the default:
container run --name my-container \
--init-image local/custom-init:latest \
alpine:latest \
echo "hello"
The Sources/Services/ContainerAPIService/Client/Utility.swift file handles the resolution of the init image reference and passes it to the VM configuration during the boot sequence.
Step 5: Verify the Custom Init Execution
Check the boot logs to confirm your custom init ran before the container process:
container logs --boot my-container | grep custom-init
Expected output:
[ 0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===
Key Implementation Files
Understanding the source code helps troubleshoot custom init issues:
Sources/ContainerPersistence/ContainerSystemConfig.swift: Defines the defaultvminitimage reference and system configuration structure.Sources/Services/ContainerAPIService/Client/Utility.swift: Resolves the--init-imageflag value and translates it into the VM boot configuration.Sources/ContainerCommands/System/SystemStart.swift: Contains theinstallInitialFilesystem(initImage:)method that mounts the custom init image during VM startup.docs/how-to.md: Provides the canonical examples for creating custom init images.docs/command-reference.md: Documents the--init-imageflag forcontainer runandcontainer createcommands.
Summary
- The custom init image feature replaces the default
vminitdbootstrap filesystem with a user-provided image mounted as the VM's root filesystem. - Use it when you need pre-boot logic, VM-level daemons, debugging instrumentation, or security hardening before the OCI container starts.
- Implement it by creating a wrapper binary that executes your logic and then calls the real
vminitd, packaging it into a container image, and specifying it with--init-image. - The system resolves the image in
Utility.swiftand mounts it viainstallInitialFilesystem(initImage:)inSystemStart.swift.
Frequently Asked Questions
What is the difference between the init image and the container image?
The init image contains the vminitd binary that runs as the VM's PID 1 to bootstrap the environment, while the container image contains the actual application or service you want to run. The init image executes first, performs setup, and then hands off to the container image's entrypoint. They are separate filesystems: the init image is the VM's root filesystem, and the container image is mounted separately.
Can I use any base image for a custom init image?
No, you must base your custom init image on the official vminit image (e.g., ghcr.io/apple/containerization/vminit:0.33.3) or ensure it contains a compatible vminitd binary and the necessary device nodes (like /dev/kmsg). The VM expects specific init behavior and filesystem structures to successfully boot.
How do I debug a custom init image that fails to start?
Check the VM boot logs using container logs --boot <container-name> to see kernel messages and any output from your custom init binary. You can also modify your wrapper to write detailed logs to /dev/kmsg or pause execution (e.g., by sleeping) to inspect the VM state before it exits. Since the custom init runs before the container's PID 1, errors here prevent the container from starting entirely.
Does using a custom init image affect container performance?
The custom init image adds negligible overhead during boot, as it only runs once at VM startup. However, if your init binary performs heavy computation or starts long-running background processes, it may delay container startup and consume VM resources. Keep init logic minimal and fast to maintain the lightweight characteristics of the Container VM.
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 →