How to Use Custom Init Images to Run VM-Level Daemons Before Container Start in Apple Container
You can execute VM-level daemons before your container's main process starts by supplying a custom init image via the --init-image flag, which replaces the default vminitd binary with a wrapper that runs your custom logic before delegating to the original init system.
Apple Container launches each workload inside a lightweight virtual machine (VM) that boots using a default init image containing the vminitd binary. By creating and specifying custom init images, you can inject arbitrary code—such as eBPF filters, kernel log emitters, or system daemons—into the VM boot sequence before the OCI container's PID 1 launches.
Understanding the Init Image Architecture
By default, Apple Container boots VMs using the vminitd binary packaged in the vminit image (ghcr.io/apple/containerization/vminit:<tag>). This binary acts as the VM's init system, responsible for setting up the container environment and launching the actual workload.
The --init-image flag allows you to substitute this default filesystem image with your own. When you provide a custom init image, the VM boots using your specified filesystem, executing your custom logic before the standard container initialization proceeds. This enables you to run arbitrary code at the VM level—such as starting background daemons, loading kernel modules, or configuring network interfaces—before the container's entrypoint executes.
Building a Custom Init Image
To create a functional custom init image, you must preserve the original vminitd functionality while inserting your own initialization logic. The standard approach involves creating a wrapper binary that executes your custom code and then delegates to the real vminitd binary.
Step 1 – Create the Wrapper Binary
Write a wrapper program that performs your VM-level initialization tasks and then executes the original vminitd binary. The following Go example writes a message to the kernel log before handing control to the standard init process:
// wrapper.go
package main
import (
"os"
"syscall"
)
func main() {
// Example: write a 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()
}
// Now launch the real vminitd
err = syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ())
if err != nil {
os.Exit(1)
}
}
Build the binary for your target architecture (for example, arm64):
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go
Step 2 – Construct the Containerfile
Create a Containerfile that preserves the original vminitd binary as /sbin/vminitd.real while installing your wrapper as /sbin/vminitd. Use the same version tag that your container runtime expects:
# Use the same version as the container runtime expects
FROM ghcr.io/apple/containerization/vminit:0.34.0 AS base
FROM ghcr.io/apple/containerization/vminit:0.34.0
COPY --from=base /sbin/vminitd /sbin/vminitd.real # preserve the original
COPY wrapper /sbin/vminitd # replace with our wrapper
Step 3 – Build the Custom Init Image
Build the image using the Apple Container CLI:
container build -t local/custom-init:latest .
Running Containers with Custom Init Images
Once you have built your custom init image, you can deploy it using the --init-image flag to inject your VM-level daemons into the boot sequence.
Using the --init-image Flag
Invoke container run with the --init-image parameter pointing to your custom image:
container run \
--name my-container \
--init-image local/custom-init:latest \
alpine:latest \
echo "hello"
The VM boots using your wrapper as the init process, executes your custom code, and then delegates to the standard vminitd.real to complete the normal container initialization.
Inspecting Boot Logs
Verify that your custom init logic executed by examining the VM boot logs:
container logs --boot my-container | grep custom-init
You should see output confirming your daemon ran:
[ 0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===
Setting Default Init Images
You can configure a default custom init image for all containers by modifying the system configuration file. According to docs/container-system-config.md, add a [vminit] section to specify the default init image path, eliminating the need to pass --init-image with every command.
Summary
- Custom init images replace the default
vminitdbinary in Apple Container's lightweight VMs, allowing you to run code before the container's PID 1 starts. - The wrapper pattern requires preserving the original binary as
/sbin/vminitd.realand installing your wrapper at/sbin/vminitd. - Build custom init images using standard
container buildcommands with a Containerfile based on the officialvminitimage. - Use the
--init-imageflag when running containers to specify your custom init image. - Verify execution using
container logs --bootto inspect kernel messages and boot output.
Frequently Asked Questions
What is the default init image used by Apple Container?
Apple Container uses the ghcr.io/apple/containerization/vminit:<tag> image by default, which contains the vminitd binary that serves as the VM's init process. This binary is responsible for initializing the container environment before launching the workload.
Can I run multiple daemons in a custom init image?
Yes. Your wrapper binary can start multiple background processes—such as logging agents, eBPF filters, or networking daemons—before executing vminitd.real. Ensure these daemons either run in the background or fork properly to avoid blocking the init handoff.
How do I debug boot issues with custom init images?
Use the container logs --boot <container-name> command to inspect the VM boot logs. This output includes kernel messages and any output from your custom init wrapper, allowing you to trace failures in the initialization sequence before the main container process starts.
Is it possible to set a custom init image as the default?
Yes. According to docs/container-system-config.md, you can configure a default init image by adding a [vminit] section to the system configuration file. This setting applies the custom init image to all containers without requiring the --init-image flag on each run command.
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 →