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 vminitd with 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:

Summary

  • The custom init image feature replaces the default vminitd bootstrap 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.swift and mounts it via installInitialFilesystem(initImage:) in SystemStart.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:

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 →