This guide explains how to build custom executor images for KubeOpenCode.
KubeOpenCode uses a two-container pattern for executing AI-powered tasks:
- OpenCode Image (Init Container): Contains the OpenCode CLI, copies it to a shared volume
- Executor Image (Worker Container): User's development environment that uses the OpenCode tool
This design separates the AI tool (OpenCode) from the execution environment, allowing users to bring their own toolsets while using a single, maintained AI agent.
┌─────────────────────────────────────────────────────────────┐
│ Job Pod │
├─────────────────────────────────────────────────────────────┤
│ Init Container: opencode-init │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Image: kubeopencode-agent-opencode │ │
│ │ - Contains OpenCode CLI (AI agent) │ │
│ │ - Copies opencode binary to /tools volume │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ shared volume (/tools) │
│ │
│ Main Container: worker │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Executor Images: │ │
│ │ ├── devbox (full dev environment) │ │
│ │ └── user-custom (your own toolset) │ │
│ │ │ │
│ │ Runs: /tools/opencode run "$(cat task.md)" │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| Concept | Description |
|---|---|
| Single AI Tool | OpenCode is the only AI agent, simplifying maintenance |
| Tool Injection | OpenCode binary is injected via init container |
| Custom Executors | Users provide their own execution environments |
| Separation of Concerns | AI tool vs development environment are decoupled |
| Image | Purpose | Container Type |
|---|---|---|
opencode |
OpenCode CLI (AI agent) | Init Container |
devbox |
Universal development environment | Worker (Executor) |
attach |
Lightweight image for Server mode --attach |
Worker (Server mode) |
The universal devbox image (kubeopencode-agent-devbox) provides a comprehensive development environment:
| Tool | Version | Description |
|---|---|---|
| Go | 1.25.5 | Go programming language |
| Node.js | 22.x LTS | JavaScript runtime |
| Python | 3.x | Python interpreter + pip + venv |
| golangci-lint | latest | Go linter |
| Tool | Description |
|---|---|
| kubectl | Kubernetes CLI |
| helm | Kubernetes package manager |
| gcloud | Google Cloud CLI |
| aws | AWS CLI v2 |
| docker | Docker CLI (for DinD scenarios) |
| Tool | Description |
|---|---|
| git | Version control |
| gh | GitHub CLI |
| make | Build automation |
| gcc, g++ | C/C++ compilers |
| jq | JSON processor |
| yq | YAML processor |
| vim, nano | Text editors |
| tree, htop | Utilities |
- zsh as default shell
- OpenShift compatible: Works with arbitrary UIDs (uses /tmp as HOME)
- Docker or Podman installed
- (Optional) Docker buildx for multi-arch builds
From the agents/ directory:
# Build OpenCode image (init container)
make AGENT=opencode build
# Build devbox image (executor)
make AGENT=devbox build
# Build attach image (Server mode)
make AGENT=attach build
# Multi-arch build and push
make AGENT=opencode buildx
make AGENT=devbox buildx
make AGENT=attach buildxFrom the project root:
# Same commands via project Makefile
make agent-build AGENT=opencode
make agent-build AGENT=devbox
make agent-build AGENT=attachDefault: ghcr.io/kubeopencode/kubeopencode-agent-<name>:latest
Customize with variables:
| Variable | Default | Description |
|---|---|---|
IMG_REGISTRY |
ghcr.io |
Container registry |
IMG_ORG |
kubeopencode |
Registry organization |
VERSION |
latest |
Image tag |
Example:
make AGENT=devbox build IMG_REGISTRY=docker.io IMG_ORG=myorg VERSION=v1.0.0
# Builds: docker.io/myorg/kubeopencode-agent-devbox:v1.0.0If the default devbox image doesn't meet your needs, you can create a custom executor image.
mkdir agents/my-executor# My Custom Executor Image
FROM debian:bookworm-slim
# Install your tools
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
your-custom-tools \
&& rm -rf /var/lib/apt/lists/*
# OpenShift compatibility: use /tmp as HOME for arbitrary UIDs
ENV HOME="/tmp"
ENV SHELL="/bin/bash"
# Create workspace directory
ARG WORKSPACE_DIR=/workspace
ENV WORKSPACE_DIR=${WORKSPACE_DIR}
RUN mkdir -p ${WORKSPACE_DIR} && chmod 777 ${WORKSPACE_DIR}
# Run as non-root
USER 1000:0
WORKDIR ${WORKSPACE_DIR}
CMD ["/bin/bash"]# Build
make AGENT=my-executor build
# Test locally (simulating the init container pattern)
docker run --rm \
-v /tmp/tools:/tools \
-v /tmp/task.md:/workspace/task.md:ro \
-e PATH="/tools:$PATH" \
ghcr.io/kubeopencode/kubeopencode-agent-my-executor:latest \
/tools/opencode run "$(cat /workspace/task.md)"Every executor image should follow these conventions:
- Work in
/workspacedirectory: All context files are mounted here - Support
/toolsvolume mount: OpenCode binary is injected here - Output to stdout/stderr: Results are captured as Job logs
- Exit with appropriate code: 0 for success, non-zero for failure
- OpenShift compatible: Use /tmp as HOME to support arbitrary UIDs
| Variable | Value | Description |
|---|---|---|
WORKSPACE_DIR |
/workspace |
Workspace directory path |
HOME |
/tmp |
Home directory (OpenShift compatible) |
GOPATH |
/tmp/.go |
Go workspace |
GOMODCACHE |
/tmp/.gomodcache |
Go module cache |
| Variable | Description |
|---|---|
TASK_NAME |
Name of the Task CR |
TASK_NAMESPACE |
Namespace of the Task CR |
Configure via the Agent credentials field:
apiVersion: kubeopencode.io/v1alpha1
kind: Agent
metadata:
name: my-agent
spec:
# TBD: New fields for opencode image + executor image pattern
credentials:
- name: api-key
secretRef:
name: ai-credentials
key: api-key
env: ANTHROPIC_API_KEY
- name: github-token
secretRef:
name: github-credentials
key: token
env: GITHUB_TOKENIf the devbox image doesn't include a tool you need:
FROM ghcr.io/kubeopencode/kubeopencode-agent-devbox:latest
# Add additional tools
USER root
RUN apt-get update && apt-get install -y postgresql-client \
&& rm -rf /var/lib/apt/lists/*
USER 1000:0If the tool is generally useful, consider adding it to devbox/Dockerfile and submitting a PR.
- Run as non-root: Use UID 1000 or support arbitrary UIDs
- Minimize packages: Only install what you need
- Use specific versions: Pin tool versions for reproducibility
- Credential handling: Never bake credentials into images; use Kubernetes secrets
- OpenShift compatible: Use /tmp for HOME and cache directories
Check that:
- The workspace directory is writable
- Required environment variables (API keys) are set
- The /tools volume is properly mounted
If running on OpenShift or with arbitrary UIDs:
- Ensure HOME is set to /tmp
- Use chmod 777 for directories that need to be writable
- Don't rely on specific user IDs
If a tool is missing:
- Check if it's in the devbox image
- Create a custom executor with the tools you need
- Consider contributing to the devbox image if generally useful
| Image | Approximate Size | Description |
|---|---|---|
opencode |
~500 MB | OpenCode CLI only |
devbox |
~2-3 GB | Full development environment |
attach |
~25 MB | Minimal image for Server mode (OpenCode binary + ca-certs) |
The larger size of devbox is a trade-off for having a comprehensive development environment similar to GitHub Actions runners. The attach image is intentionally minimal as it only needs to connect to an OpenCode server and stream output.