How to Use Custom Init Images for Container Boot-Time Customization in Apple Container
Use the --init-image flag to replace the default vminitd binary with a custom wrapper that executes boot-time logic before handing control to the real init process.
The apple/container project runs each container inside a lightweight VM that boots with an init filesystem image containing the vminitd binary. By leveraging custom init images for container boot-time customization, you can inject arbitrary logic—such as logging, eBPF filter loading, or daemon startup—into the very first code that executes on the VM, before any user-provided container image starts.
What Are Custom Init Images?
Each container runs inside a lightweight VM that boots with an init filesystem image providing the vminitd binary (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.
A custom init image is a small Linux filesystem containing a wrapper binary that replaces the original vminitd. This wrapper performs arbitrary boot-time work and then transfers control to the real vminitd via exec. This approach lets you instrument the VM bootstrap phase before the OCI container workload begins.
How the --init-image Flag Works
The --init-image CLI flag overrides the default init image for a single run or create operation. Internally, the flag propagates through several layers of the Swift codebase:
- Command-line parsing: In
Sources/Services/ContainerAPIService/Client/Flags.swift, theinitImageproperty captures the flag value between lines 177–268. - Runtime forwarding:
Sources/ContainerCommands/Container/ContainerRun.swiftforwards the flag to the container runtime service. - Image mounting:
Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swifthandles fetching and mounting the init filesystem before the OCI container starts.
The default unpack strategy for init images is provided by Sources/Services/ContainerAPIService/Server/SnapshotStore.swift.
Creating a Custom Init Image
Follow this workflow to build and deploy a custom init image that logs a message during VM boot.
Step 1: Write the Wrapper Binary
Create a Go wrapper that writes to the kernel log and then executes the real vminitd. This example uses Go for easy cross-compilation.
// 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)
}
}
Step 2: Build for the Target Architecture
Build the wrapper statically for the VM architecture (typically arm64).
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go
Step 3: Create the Containerfile
Use a multi-stage build that preserves the original vminitd as vminitd.real and installs your wrapper as the new /sbin/vminitd.
# 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
Step 4: Build the Custom Init Image
Build the image using the container CLI.
container build -t local/custom-init:latest .
Step 5: Run with the Custom Init Image
Pass the --init-image flag to override the default init image for your container.
container run --name my-container \
--init-image local/custom-init:latest \
alpine:latest echo "hello"
Step 6: Verify Boot-Time Execution
Inspect the VM boot logs to confirm your wrapper executed.
container logs --boot my-container | grep custom-init
Expected output:
[ 0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===
Key Implementation Details
The custom init image feature touches several critical files in the apple/container repository:
docs/how-to.md(lines 556–632): User-facing guide explaining the flag, wrapper creation, Dockerfile structure, build steps, and verification.docs/command-reference.md(lines 57–121): Formal CLI reference documenting the--init-imageflag.Sources/Services/ContainerAPIService/Client/Flags.swift(lines 177–268): Defines theinitImageproperty parsed from the command line.Sources/ContainerCommands/Container/ContainerRun.swift: Core implementation forwarding--init-imageto the runtime service.Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift: Handles fetching and mounting the init image before container startup.
Summary
- Custom init images replace the default
vminitdbinary to inject logic into the VM boot sequence. - Use the
--init-image <image>flag to specify a custom init image for a single run or create operation. - The wrapper binary must
execthe real/sbin/vminitd.realafter completing custom boot-time work. - Build your wrapper for the target VM architecture (usually
arm64). - Verify execution using
container logs --boot <name>to inspect early boot messages.
Frequently Asked Questions
What is the default init image if I don't specify --init-image?
According to the runtime configuration in the [vminit] section, the default image is ghcr.io/apple/containerization/vminit:0.34.0. This image provides the standard vminitd binary that initializes the VM environment before starting the container workload.
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 act as the VM's init process. The image should derive from the official vminit base image to ensure compatibility with the VM kernel and versioning, as demonstrated in the Dockerfile example using ghcr.io/apple/containerization/vminit:0.34.0 as both the base and final stage.
How do I debug a custom init image that fails to boot?
Use the container logs --boot <container-name> command to inspect the VM boot logs. This output captures everything from the init process startup, including any output from your wrapper binary before it executes the real vminitd. Look for kernel messages or errors related to your custom init logic.
Is the --init-image flag persistent across container restarts?
No, the --init-image flag applies only to the specific run or create operation where it is specified. It is not stored as part of the container configuration. To use a custom init image persistently, you must specify the flag every time you start or run the 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 →