# How to Use the Custom Init Image Feature in Container

> Learn how to use the custom init image feature in apple/container to replace the default bootstrap filesystem for pre-boot custom logic, debugging, and VM services.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: how-to-guide
- Published: 2026-06-12

---

**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`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/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:

```go
// 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):

```bash
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:

```dockerfile
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:

```bash
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`](https://github.com/apple/container/blob/main/docs/command-reference.md), this flag tells the runtime to use your image instead of the default:

```bash
container run --name my-container \
    --init-image local/custom-init:latest \
    alpine:latest \
    echo "hello"

```

The [`Sources/Services/ContainerAPIService/Client/Utility.swift`](https://github.com/apple/container/blob/main/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:

```bash
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`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/ContainerSystemConfig.swift)**: Defines the default `vminit` image reference and system configuration structure.
- **[`Sources/Services/ContainerAPIService/Client/Utility.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Utility.swift)**: Resolves the `--init-image` flag value and translates it into the VM boot configuration.
- **[`Sources/ContainerCommands/System/SystemStart.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/System/SystemStart.swift)**: Contains the `installInitialFilesystem(initImage:)` method that mounts the custom init image during VM startup.
- **[`docs/how-to.md`](https://github.com/apple/container/blob/main/docs/how-to.md)**: Provides the canonical examples for creating custom init images.
- **[`docs/command-reference.md`](https://github.com/apple/container/blob/main/docs/command-reference.md)**: Documents the `--init-image` flag for `container run` and `container create` commands.

## 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`](https://github.com/apple/container/blob/main/Utility.swift) and mounts it via `installInitialFilesystem(initImage:)` in [`SystemStart.swift`](https://github.com/apple/container/blob/main/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.