# How to Implement Custom Init Images for Advanced Container Startup Logic in Apple Container

> Implement custom init images for advanced container startup logic in Apple Container VMs. Use the --init-image flag to run custom code before the init process begins.

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

---

**Supply a custom OCI image containing a `/sbin/vminitd` wrapper binary using the `--init-image` flag to execute arbitrary code before the real init process starts in the Apple Container VM.**

The Apple Container CLI (`container`) runs each workload inside a lightweight virtual machine that boots with a default `vminitd` image. When you need advanced startup behavior—such as injecting debugging code, starting auxiliary daemons, or applying eBPF filters before the OCI container starts—you can implement **custom init images** that replace the default init filesystem while preserving standard PID-1 responsibilities.

## Understanding Custom Init Images

By default, the VM boots with `ghcr.io/apple/containerization/vminit:<tag>`, which provides a minimal init process that forwards signals and re-applies standard PID-1 responsibilities. According to the [`docs/how-to.md`](https://github.com/apple/container/blob/main/docs/how-to.md) file in the `apple/container` repository, a custom init image is a regular OCI image that supersedes the default `vminitd` filesystem, allowing you to run arbitrary code **before** the real init process takes over.

### Key Requirements

A valid custom init image must satisfy two critical constraints:

- **Contain `/sbin/vminitd`**: Your image must provide a binary at `/sbin/vminitd` that the VM executes as PID-1.
- **Exec the real init**: Your wrapper binary should eventually execute the real `vminitd` binary (typically renamed to `vminitd.real`) to preserve the normal container lifecycle.

The version tag for the base image is defined in [`Package.swift`](https://github.com/apple/container/blob/main/Package.swift) as `scVersion`, which determines the default `vminit` tag used by the CLI.

## Creating a Custom Init Image

Implementing a custom init image requires building a wrapper binary, constructing a Dockerfile that layers your wrapper over the official base image, and tagging the result for local use.

### Step 1: Write the Wrapper Binary

Create a wrapper that performs custom startup logic, then hands control to the real init binary. The following Go example from [`docs/how-to.md`](https://github.com/apple/container/blob/main/docs/how-to.md) writes a marker to the kernel log before executing the original `vminitd`:

```go
// wrapper.go
package main

import (
    "os"
    "syscall"
)

func main() {
    // Log a custom 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()
    }

    // Replace the process with the real vminitd binary
    if err := syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ()); err != nil {
        os.Exit(1)
    }
}

```

This wrapper captures the original arguments and environment, allowing seamless delegation to `vminitd.real` after your custom logic executes.

### Step 2: Build for the Target Architecture

The container VM runs on `arm64` (Apple Silicon) by default. Build the wrapper with cross-compilation settings matching your target architecture:

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

```

If you are targeting a different architecture, adjust `GOARCH` accordingly before building.

### Step 3: Construct the Init Image Dockerfile

Layer your wrapper over the official `vminit` image. This Dockerfile pattern preserves the original binary by copying it to `/sbin/vminitd.real`, then replaces `/sbin/vminitd` with your wrapper:

```dockerfile

# Use the same vminit tag as defined in Package.swift

FROM ghcr.io/apple/containerization/vminit:0.34.0 AS base

FROM ghcr.io/apple/containerization/vminit:0.34.0

# Preserve the original binary

COPY --from=base /sbin/vminitd /sbin/vminitd.real

# Replace it with our wrapper

COPY wrapper /sbin/vminitd

```

This approach ensures the VM can still access the original init logic after your wrapper completes its execution.

### Step 4: Build and Tag the Custom Image

Use the `container build` command to create the image locally:

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

```

The resulting image contains your wrapper at `/sbin/vminitd` and the preserved original at `/sbin/vminitd.real`.

## Running Containers with Your Custom Init Image

Once built, reference your custom init image when launching containers to intercept the VM boot process.

### Using the --init-image Flag

Pass the `--init-image` flag to `container run` to specify your custom init image:

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

```

The CLI loads the specified init image into the VM before launching the OCI container. The VM boots your custom image, executes your wrapper as PID-1, and then hands control to the real init process via `syscall.Exec`.

### Verifying Custom Init Execution

Confirm your wrapper executed by inspecting the VM boot logs. The example wrapper writes to `/dev/kmsg`, which appears in the boot log stream:

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

```

Expected output:

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

```

This verification confirms your custom logic ran before the main container process started.

## Advanced Use Cases and Considerations

Custom init images provide a hook that runs **once per VM boot**, making them ideal for several advanced scenarios:

- **Debugging instrumentation**: Inject diagnostic tools or tracing agents before the workload starts.
- **Security policies**: Apply eBPF filters or seccomp profiles early in the boot process.
- **Auxiliary services**: Launch sidecar daemons that must be available before the main container initializes.

As implemented in `apple/container`, the [`scripts/install-init.sh`](https://github.com/apple/container/blob/main/scripts/install-init.sh) utility can build a local copy of the base `vminit` image when you need to inspect the default behavior or modify the base layer. Additionally, the [`docs/container-system-config.md`](https://github.com/apple/container/blob/main/docs/container-system-config.md) file details how the `vminit` configuration block determines which init image the system uses by default.

## Summary

- **Custom init images** in Apple Container allow arbitrary code execution during the VM boot phase before the OCI container starts.
- Your custom image must provide a `/sbin/vminitd` binary that eventually execs `/sbin/vminitd.real` to preserve the standard init lifecycle.
- Build a wrapper binary for `arm64` (or your target architecture), layer it over the official `ghcr.io/apple/containerization/vminit` image, and tag it locally.
- Launch containers with `container run --init-image <image>` and verify execution via `container logs --boot`.

## Frequently Asked Questions

### What happens if my custom init image does not contain /sbin/vminitd?

The VM will fail to boot because the container runtime expects to execute `/sbin/vminitd` as the init process. Always ensure your image provides this binary, even if it is a wrapper that eventually chains to another executable.

### Can I use a custom init image with any OCI container image?

Yes. The `--init-image` flag is independent of the container image you specify. You can use a custom init image with any OCI-compliant image (such as `alpine:latest` or `ubuntu:latest`) as the workload.

### How do I determine which vminit tag to use in my Dockerfile?

Check the `scVersion` constant in [`Package.swift`](https://github.com/apple/container/blob/main/Package.swift) at the root of the `apple/container` repository. This variable defines the default tag used by the CLI, ensuring compatibility between your custom init and the runtime expectations.

### Is the custom init image executed for every container restart?

The custom init runs **once per VM boot**, not per container restart. Since the Apple Container architecture runs containers inside lightweight VMs, the init process only executes when the VM first boots. If the container stops but the VM remains running, the init does not re-execute.