# How to Use Custom Init Images for Boot Customization in Apple's Container Runtime

> Customize boot logic in Apple's container runtime using custom init images. Use the --init-image flag to replace vminitd and execute your own boot-time operations.

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

---

**Use the `--init-image <image>` flag to replace the default `vminitd` binary with a custom wrapper that executes arbitrary boot-time logic before handing control to the real init process.**

Apple's `container` runtime executes each container inside a lightweight VM that boots with an init filesystem image containing the `vminitd` binary. By leveraging custom init images, you can inject specialized initialization logic—such as logging, eBPF filter loading, or auxiliary daemon startup—into the VM boot sequence before the OCI container image starts.

## Understanding the Init Image Architecture

The `container` runtime launches containers within lightweight virtual machines. Each VM boots with an **init filesystem image** that provides the `vminitd` binary, which serves as 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`.

When you specify a custom init image, the runtime fetches and mounts your filesystem in place of the default. The VM then executes your custom binary as PID 1, allowing you to instrument the very first code that runs before any user-provided container workload begins.

## Creating a Custom Init Image

A custom init image is a small Linux filesystem containing a wrapper binary that replaces the original `vminitd`. The wrapper performs your custom boot-time work and then transfers control to the real `vminitd` via `exec`.

### Writing the Wrapper Binary

The wrapper must handle the transition to the real init system. Below is a minimal Go implementation that logs a message to the kernel ring buffer before handing execution to the original binary.

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

```

Build the wrapper for the target architecture (typically `arm64` for Apple Silicon):

```bash
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go

```

### Building the Container Image

Create a Dockerfile that uses the same `vminit` base image as the runtime to ensure kernel compatibility. The Dockerfile preserves the original `vminitd` as `vminitd.real` and installs your wrapper in its place.

```dockerfile

# 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

```

Build the image using the `container` CLI:

```bash
container build -t local/custom-init:latest .

```

## Deploying with the --init-image Flag

The `--init-image` flag overrides the default init image for a single run or create operation. In [`Sources/Services/ContainerAPIService/Client/Flags.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Flags.swift), the property `initImage` captures the flag value and propagates it through the command-line parsing layer. This value is forwarded via [`Sources/ContainerCommands/Container/ContainerRun.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ContainerRun.swift) to the runtime service.

In [`Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift), the runtime fetches the specified image and mounts it as the init filesystem before starting the OCI container.

Run a container with your custom init image:

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

```

## Verifying Boot-Time Execution

To confirm your custom init logic executed during the VM boot sequence, inspect the VM boot logs using the `--boot` flag. The wrapper's early execution means its output appears in the kernel log before container runtime logs begin.

```bash
container logs --boot my-container | grep custom-init

```

Expected output:

```

[    0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===

```

## Summary

- **Custom init images** replace the default `vminitd` binary in Apple's container runtime, enabling boot-time customization inside the lightweight VM.
- The **`--init-image <image>`** flag routes through [`Flags.swift`](https://github.com/apple/container/blob/main/Flags.swift) and [`ContainerRun.swift`](https://github.com/apple/container/blob/main/ContainerRun.swift) to [`ContainersService.swift`](https://github.com/apple/container/blob/main/ContainersService.swift), which mounts your filesystem before container startup.
- **Implementation requires** a wrapper binary that performs custom work and `exec`s the original `/sbin/vminitd.real`, packaged in a container image based on the official `vminit` base.
- **Verification** uses `container logs --boot <name>` to inspect kernel messages written before the container workload begins.

## Frequently Asked Questions

### What is the default init image used by Apple's container runtime?

The default init image is defined in the runtime configuration under the `[vminit]` section, typically referencing `ghcr.io/apple/containerization/vminit:0.34.0`. This image contains the `vminitd` binary that serves as the VM's init process.

### 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 function as PID 1 within the VM. Apple recommends basing your image on the same `vminit` version that ships with the runtime to ensure compatibility with the VM kernel and versioning constraints.

### How do I troubleshoot a custom init image that fails to boot?

Check the VM boot logs using `container logs --boot <container-name>` to see if your wrapper produced any output before failing. Ensure your wrapper correctly `exec`s `/sbin/vminitd.real` with the original arguments and environment. Verify that the wrapper binary is compiled for the correct architecture (usually `arm64`) and has executable permissions.

### Is the --init-image flag available for container create operations?

Yes. The `--init-image` flag works for both `container run` and `container create` operations. In [`Sources/Services/ContainerAPIService/Client/Flags.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Flags.swift), the `initImage` property is parsed and passed to the runtime service regardless of whether you are running or creating a container.