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 vminitd binary 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.real and installing your wrapper at /sbin/vminitd.
  • Build custom init images using standard container build commands with a Containerfile based on the official vminit image.
  • Use the --init-image flag when running containers to specify your custom init image.
  • Verify execution using container logs --boot to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →