Skip to content

openkaiden/openshell-image-builder

Repository files navigation

openshell-image-builder

OpenShell is NVIDIA's runtime environment for autonomous AI agents. It provides isolated sandboxes where agents can safely run and iterate — without risk to the host system or your credentials.

OpenShell ships a set of pre-built sandbox images, but they are general-purpose. openshell-image-builder lets you build your own: lightweight, workspace-specific images that contain only what you need — without writing a Containerfile by hand.

The tool assembles the image in layers — base image, agent installation, agent settings, OpenShell network policy, and project-specific toolchains:

  1. Base image — Ubuntu, Fedora, Red Hat UBI, or Red Hat Hardened Images (HummingBird), any tag. Ubuntu 24.04 is the default.
  2. Agent installation (--agent) — the agent binary is pre-installed in PATH.
  3. Agent settings
    • User settings — settings files are pre-populated with settings files provided by the user.
    • Auto-onboarding — settings files are updated to skip onboarding steps (choose theme, trust directory, etc).
    • Skills — skills are copied in the agent's skills directory.
    • Inference settings (--inference) — inference provider definition is added to settings files.
    • Endpoint override (--endpoint) — optional custom URL for inference provider is set in inference provider definition.
    • Model (--model) — default model is baked into the agent's settings files.
  4. OpenShell policy/etc/openshell/policy.yaml shipped with every image.
    • Base policy — Git operations over HTTPS and the GitHub REST API.
    • Agent network rules — agent-specific endpoints are added by --agent.
    • Inference network rules — LLM backend endpoints are added by --inference.
  5. Installation of project-specific toolchains — toolchains and utilities declared as Dev Container Features in .kaiden/workspace.json are installed in the image.

Agent Supported Features

Agent User settings Auto-onboarding Skills
claude Yes Yes Yes
~/.claude/skills/
opencode Yes N/A Yes
~/.opencode/skills/

Agent × Inference Supported Features

Agent Inference Inference settings Endpoint override Model selection
claude anthropic N/A Yes
ENV ANTHROPIC_BASE_URL
Yes
model in .claude/settings.json
claude vertexai N/A No
fixed endpoint
Yes
model in .claude/settings.json
opencode anthropic N/A, Yes if endpoint override Yes
opencode config baseURL
Yes
model in .config/opencode/config.json
opencode vertexai N/A No
fixed endpoint
Yes
model in .config/opencode/config.json
opencode ollama Yes
Ollama provider config
Yes
baseURL in Ollama provider config
Yes
model in .config/opencode/config.json
opencode openai Yes if model or endpoint Yes
baseURL in custom provider config
Yes
model in .config/opencode/config.json

Quick start

Build an image with a single command:

openshell-image-builder myimage:latest

<TAG> is the only required argument — it sets the tag for the built image. By default, the tool uses Ubuntu 24.04 as the base image.

Configuring the base image

To use a different base image or tag, create a configuration file.

File location

The tool looks for a config.toml file in this order, using the first directory found:

  1. Directory given by the --config flag
  2. Directory set in the OPENSHELL_IMAGE_BUILDER_CONFIG environment variable
  3. The platform config directory:
    • Linux: $XDG_CONFIG_HOME/openshell-image-builder/ (defaults to ~/.config/openshell-image-builder/)
    • macOS: ~/Library/Application Support/openshell-image-builder/
    • Windows: %APPDATA%\openshell-image-builder\

If no config.toml is found in the resolved directory, or the file is empty, built-in defaults are used.

If a directory is given explicitly (via --config or the environment variable) but it does not exist, the command fails immediately.

Base images

Ubuntu (default)

[openshell_image_builder.base_image]
image = "ubuntu"
tag   = "24.04"

Fedora

[openshell_image_builder.base_image]
image = "fedora"
tag   = "latest"

Red Hat UBI

[openshell_image_builder.base_image]
image = "ubi"
tag   = "latest"

Red Hat Hardened Images (Hummingbird)

[openshell_image_builder.base_image]
image = "hummingbird"
tag   = "latest-builder"

Full schema reference

[openshell_image_builder]
version = 1

[openshell_image_builder.base_image]
image = "ubuntu"   # "ubuntu", "fedora", "ubi", or "hummingbird"
tag   = "24.04"
Field Default Description
openshell_image_builder.version 1 Configuration schema version
openshell_image_builder.base_image.image ubuntu Base image name (ubuntu, fedora, ubi, or hummingbird)
openshell_image_builder.base_image.tag 24.04 Base image tag — Ubuntu: 24.04, 22.04, …; Fedora: latest, 43, 42, …; UBI: latest, 10.2-1780377767, …; Hummingbird: latest-builder, …

Loading from a specific config directory

Pass --config to point to a directory explicitly (the tool reads config.toml inside it):

openshell-image-builder --config /path/to/config/dir myimage:latest

Or set the environment variable instead:

OPENSHELL_IMAGE_BUILDER_CONFIG=/path/to/config/dir openshell-image-builder myimage:latest

Logging

Use -v (info) or -vv (debug) to increase log verbosity — useful for tracing which config file is loaded:

openshell-image-builder -v myimage:latest

Installing an agent

Pass --agent to install an agent into the image.

Agent Value Description
Claude Code claude Anthropic's Claude Code CLI
OpenCode opencode OpenCode AI coding agent
openshell-image-builder --agent claude myimage:latest
openshell-image-builder --agent opencode myimage:latest

Agent settings

You can pre-populate the sandbox home directory with settings files specific to an agent. Place the files under:

<settings dir>/agents/<agent>/

where <settings dir> is the directory described in Configuring the base image, and <agent> matches the value passed to --agent (claude or opencode).

All files and subdirectories are copied into /sandbox/ (the sandbox user's home directory), owned by the sandbox user. The copy happens before the agent is installed, so the agent installer can create additional files on top without overwriting your settings.

Example — Claude Code settings file

mkdir -p ~/.config/openshell-image-builder/agents/claude/.claude
cp ~/.claude/settings.json \
   ~/.config/openshell-image-builder/agents/claude/.claude/settings.json

openshell-image-builder --agent claude myimage:latest

The file will be present at /sandbox/.claude/settings.json in the image.

Example — OpenCode settings file

mkdir -p ~/.config/openshell-image-builder/agents/opencode/.config/opencode
cp ~/.config/opencode/config.json \
   ~/.config/openshell-image-builder/agents/opencode/.config/opencode/config.json

openshell-image-builder --agent opencode myimage:latest

The file will be present at /sandbox/.config/opencode/config.json in the image.

Automatic configuration — Claude Code

When --agent claude is used, the builder automatically creates or updates /sandbox/.claude.json with the following settings to skip the interactive onboarding dialogs that would otherwise appear on first launch:

  • hasCompletedOnboarding: true — marks the setup wizard as complete.
  • projects["/sandbox"].hasTrustDialogAccepted: true — pre-accepts the workspace trust prompt for the sandbox home directory.

If you provide your own .claude.json in the agent settings directory, the builder merges these fields into it, preserving any other fields you have set.

Configuring inference

Pass --inference to allow the agent to reach its LLM backend. This is separate from --agent because the same inference provider can serve multiple agents.

Inference Value Agents Description
Anthropic anthropic claude, opencode Anthropic API (api.anthropic.com)
Vertex AI vertexai claude, opencode Google Vertex AI (oauth2.googleapis.com, aiplatform.googleapis.com, *-aiplatform.googleapis.com)
Ollama ollama opencode Local models on the host machine, reached via host.openshell.internal:11434
OpenAI openai opencode OpenAI API (api.openai.com), or any OpenAI-compatible endpoint via --endpoint
openshell-image-builder --agent claude --inference anthropic myimage:latest
openshell-image-builder --agent opencode --inference anthropic myimage:latest
openshell-image-builder --agent claude --inference vertexai myimage:latest
openshell-image-builder --agent opencode --inference vertexai myimage:latest
openshell-image-builder --agent opencode --inference ollama myimage:latest
openshell-image-builder --agent opencode --inference openai myimage:latest

Custom endpoint (--endpoint)

Use --endpoint to override the inference provider's default URL — useful for routing through a proxy, a local instance, or a non-default port.

Agent Inference Supported Effect
claude anthropic Baked into the image as ENV ANTHROPIC_BASE_URL=<url>
claude vertexai Rejected — Vertex AI has a proprietary fixed endpoint
opencode anthropic Written to opencode config as provider.anthropic.options.baseURL
opencode vertexai Rejected — Vertex AI has a proprietary fixed endpoint
opencode ollama Written to opencode config as provider.ollama.options.baseURL; localhost in the URL is rewritten to host.openshell.internal; defaults to http://host.openshell.internal:11434/v1 if omitted
opencode openai When provided, opencode is configured to use a custom @ai-sdk/openai-compatible provider with options.baseURL set to the given URL
# Route Claude Code through a custom Anthropic API proxy
openshell-image-builder \
  --agent claude --inference anthropic \
  --endpoint https://my-anthropic-proxy.example.com \
  myimage:latest

# Route OpenCode through a custom Anthropic API proxy
openshell-image-builder \
  --agent opencode --inference anthropic \
  --endpoint https://my-anthropic-proxy.example.com \
  myimage:latest

# Connect OpenCode to Ollama running on a non-default port
openshell-image-builder \
  --agent opencode --inference ollama \
  --endpoint http://localhost:9999/v1 \
  myimage:latest

Default model (--model)

Use --model to bake a default model into the image. The agent uses this model without requiring a runtime flag.

Agent Inference Effect
claude any Written to .claude/settings.json as "model": "<model>"
opencode anthropic Written to opencode config as top-level "model" field (can be combined with --endpoint)
opencode vertexai Written to opencode config as top-level "model" field
opencode ollama Written to opencode config as top-level "model": "ollama/<model>" field; only the specified model is registered in the models map
opencode openai Written to opencode config as "model": "openai/<model>" (native OpenAI) or "model": "custom/<model>" (with --endpoint)
# Pin Claude Code to a specific model
openshell-image-builder \
  --agent claude --inference anthropic \
  --model claude-opus-4-8 \
  myimage:latest

# Pin OpenCode to a specific Anthropic model
openshell-image-builder \
  --agent opencode --inference anthropic \
  --model claude-opus-4-8 \
  myimage:latest

# Pin OpenCode to a specific Ollama model
openshell-image-builder \
  --agent opencode --inference ollama \
  --model qwen3-coder:30b \
  myimage:latest

The model string is passed through as-is — use whatever identifier your agent and provider expect.

Automatic configuration — OpenCode + Ollama

When --agent opencode --inference ollama is used, the builder automatically writes /sandbox/.config/opencode/config.json to configure OpenCode's Ollama provider:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "baseURL": "http://host.openshell.internal:11434/v1"
      },
      "models": {
        "lfm2.5":          { "tools": true },
        "qwen3-coder:30b": { "tools": true }
      }
    }
  }
}

When --model is also given, the top-level "model" field is added (as "ollama/<model>") and the models map is replaced with a single entry for the specified model.

host.openshell.internal is the hostname used inside the sandbox to reach the container host, where Ollama is expected to be running on its default port (11434). Ollama must be running on the host before the sandbox is started.

Sandbox policy

Every image built by this tool includes /etc/openshell/policy.yaml. This file is read by the OpenShell runtime and defines the sandbox security policy for the container:

  • Filesystem policy — which paths are read-only, read-write, or inaccessible to the sandbox user.
  • Network policies — which binaries are allowed to connect to which hosts and ports.

The policy is built in three layers, merged in order:

  1. Base (assets/policy.yaml) — general-purpose tooling: Git operations over HTTPS and the GitHub REST API via gh.
  2. Inference (added by --inference) — LLM backend endpoints scoped to the agent binary. For example, --inference anthropic adds api.anthropic.com and statsig.anthropic.com; --inference vertexai adds oauth2.googleapis.com and aiplatform.googleapis.com (including the *-aiplatform.googleapis.com wildcard); --inference ollama adds host.openshell.internal:11434 for local model access; --inference openai adds api.openai.com (or the custom endpoint host when --endpoint is used).
  3. Agent (added by --agent) — agent-specific endpoints. For example, --agent claude adds platform.claude.com, raw.githubusercontent.com, and the GitHub REST API for Claude's coding tools; --agent opencode adds opencode.ai, registry.npmjs.org, and models.dev.

Dev Container Features

The tool supports Dev Container Features declared in .kaiden/workspace.json in the current directory.

workspace.json schema

{
  "features": {
    "<feature-ref>": {
      "<option>": "<value>"
    }
  }
}

Each key in features is a feature reference; each value is a map of options passed to the feature's install.sh.

Feature references

Format Example Resolves to
OCI registry reference ghcr.io/devcontainers/features/rust:1 downloaded from registry
Local path ./my-feature .kaiden/my-feature/

Local paths are resolved relative to .kaiden/: ./my-feature points to .kaiden/my-feature/.

OCI references without an explicit registry default to ghcr.io. Tags and digests (@sha256:…) are both supported. Direct http:// / https:// tarball URLs are not supported.

Example

{
  "features": {
    "ghcr.io/devcontainers/features/rust:1": {
      "version": "stable",
      "profile": "minimal"
    },
    "./my-feature": {}
  }
}

With the above, ./my-feature refers to a local feature at .kaiden/my-feature/.

Installation order

Features are installed in the order defined by each feature's installsAfter field in its devcontainer-feature.json. Within the same dependency level, features are processed in alphabetical order by reference.

Skills

Agents can be extended with skills — named toolkits that an agent discovers at startup. Declare skill directories in .kaiden/workspace.json:

{
  "skills": [
    "./my-skill",
    "./.kaiden/skills/another-skill"
  ]
}

Each entry is a path to a directory (relative to the workspace root). The directory name becomes the skill name in the image.

During the build, each skill directory is copied into the agent's skills directory:

Agent Skills directory
claude /sandbox/.claude/skills/
opencode /sandbox/.opencode/skills/

With --agent claude and "skills": ["./my-skill"], the skill lands at /sandbox/.claude/skills/my-skill/ in the image, owned by the sandbox user.

Skills without a corresponding --agent flag are silently ignored — the agent determines where skills go, so a build without an agent produces no skill COPY instructions.

How it works

When .kaiden/workspace.json is present, the tool:

  1. Downloads and extracts each OCI feature into a temporary build context directory (/tmp/openshell-image-builder…).
  2. Copies local feature directories into the same build context.
  3. Passes the build context to podman build, where each feature is installed via:
    COPY features/<dir>/ /tmp/feature-install/<dir>/
    RUN chmod +x /tmp/feature-install/<dir>/install.sh && \
        OPTION="value" /tmp/feature-install/<dir>/install.sh
  4. Cleans up all feature files from the image with RUN rm -rf /tmp/feature-install after all features are installed.

Features run as root so install scripts can write to system paths.

Full option reference

openshell-image-builder [OPTIONS] <TAG>
Argument / Option Description
<TAG> Tag for the built image (e.g. myimage:latest)
--config <CONFIG> Path to config directory containing config.toml (env: OPENSHELL_IMAGE_BUILDER_CONFIG)
--agent <AGENT> Agent to install in the image (claude, opencode)
--inference <INFERENCE> Inference server the agent will connect to (anthropic, vertexai, ollama, openai)
--endpoint <URL> Override the inference provider's default endpoint URL (see Custom endpoint)
--model <MODEL> Default model for the agent to use (see Default model)
-v / -vv Increase log verbosity (info / debug)

Examples

Claude Code agent + Anthropic models provider

$ openshell-image-builder \
  --agent claude \
  --inference anthropic \
  --model claude-sonnet-4-6 \
  sandbox_image:claude_anthropic

$ openshell provider create \
  --type generic \
  --credential ANTHROPIC_API_KEY=sk-ant-... \
  --name claude_anthropic_provider

$ openshell sandbox create \
  --from sandbox_image:claude_anthropic \
  --provider claude_anthropic_provider \
  --upload . \
  --name claude_anthropic_sandbox \
  --no-auto-providers \
  -- claude

OpenCode agent + Anthropic models provider

$ openshell-image-builder \
  --agent opencode \
  --inference anthropic \
  --model claude-sonnet-4-6 \
  sandbox_image:opencode_anthropic

$ openshell provider create \
  --type generic \
  --credential ANTHROPIC_API_KEY=sk-ant-... \
  --name opencode_anthropic_provider

$ openshell sandbox create \
  --from sandbox_image:opencode_anthropic \
  --provider opencode_anthropic_provider \
  --upload . \
  --name opencode_anthropic_sandbox \
  --no-auto-providers \
  -- opencode

Claude Code agent + Vertex AI models provider

$ openshell-image-builder \
  --agent claude \
  --inference vertexai \
  --model claude-sonnet-4-6 \
  sandbox_image:claude_vertexai

# change value of ANTHROPIC_VERTEX_PROJECT_ID and CLOUD_ML_REGION
$ openshell sandbox create \
  --from sandbox_image:claude_vertexai \
  --upload . \
  --name claude_vertexai_sandbox \
  --no-auto-providers \
  --no-tty \
  -- bash -c '(\
    echo export CLAUDE_CODE_USE_VERTEX=1; \
    echo export ANTHROPIC_VERTEX_PROJECT_ID=my-gcp-project; \
    echo export CLOUD_ML_REGION=global \
  ) >> /sandbox/.bashrc'

$ openshell sandbox upload \
  claude_vertexai_sandbox \
  $HOME/.config/gcloud/application_default_credentials.json \
  /sandbox/.config/gcloud/application_default_credentials.json

$ openshell sandbox connect claude_vertexai_sandbox

sandbox:~$ claude

OpenCode agent + Ollama (local models)

Ollama must be running on the host before starting the sandbox.

$ openshell-image-builder \
  --agent opencode \
  --inference ollama \
  --model qwen3-coder:30b \
  sandbox_image:opencode_ollama

$ openshell sandbox create \
  --from sandbox_image:opencode_ollama \
  --upload . \
  --name opencode_ollama_sandbox \
  --no-auto-providers \
  -- opencode

OpenCode agent + OpenAI models provider

$ openshell-image-builder \
  --agent opencode \
  --inference openai \
  --model gpt-4o \
  sandbox_image:opencode_openai

$ openshell provider create \
  --type generic \
  --credential OPENAI_API_KEY=sk-... \
  --name opencode_openai_provider

$ openshell sandbox create \
  --from sandbox_image:opencode_openai \
  --provider opencode_openai_provider \
  --upload . \
  --name opencode_openai_sandbox \
  --no-auto-providers \
  -- opencode

To use an OpenAI-compatible endpoint (e.g. Azure OpenAI, a local proxy, or another provider's API):

$ openshell-image-builder \
  --agent opencode \
  --inference openai \
  --endpoint https://my-openai-proxy.example.com/v1 \
  --model gpt-4o \
  sandbox_image:opencode_openai_custom

OpenCode agent + Vertex AI models provider

$ openshell-image-builder \
  --agent opencode \
  --inference vertexai \
  --model claude-sonnet-4-6 \
  sandbox_image:opencode_vertexai

# change value of GOOGLE_CLOUD_PROJECT and VERTEX_LOCATION
$ openshell sandbox create \
  --from sandbox_image:opencode_vertexai \
  --upload . \
  --name opencode_vertexai_sandbox \
  --no-auto-providers \
  --no-tty \
  -- bash -c '(\
    echo export GOOGLE_CLOUD_PROJECT=my-gcp-project; \
    echo export VERTEX_LOCATION=global; \
    echo export GOOGLE_APPLICATION_CREDENTIALS=/sandbox/.config/gcloud/application_default_credentials.json \
  ) >> /sandbox/.bashrc'

$ openshell sandbox upload \
  opencode_vertexai_sandbox \
  $HOME/.config/gcloud/application_default_credentials.json \
  /sandbox/.config/gcloud/application_default_credentials.json

$ openshell sandbox connect opencode_vertexai_sandbox

sandbox:~$ opencode

# select a model with /models

About

A cli to build an image for openshell sandbox based on kaiden workspace config

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages