Build and deploy containerized AI code agents with language-specific tooling.
This repository builds a container image for OpenCode — an AI coding agent that supports Claude via Vertex AI, Gemini, and OpenAI as selectable providers. The image includes Node.js, Go, and Python 3 and can be run locally with Podman.
opencode- OpenCode with Node.js, Go, and Python 3
Each image includes:
- Git and essential development tools
- Node.js 24 (Red Hat UBI10 base)
- OpenCode CLI pre-installed (
opencode-ai) - Provider config for Claude, Gemini, and OpenAI
- Go and Python 3 runtimes
- Development utilities (fzf, ripgrep, jq, vim, nano, zsh, less)
- Podman or Docker (for building)
- Make (for running build/deploy targets)
- Git
Credentials are not required to build the image. API credentials are required only when running OpenCode with a provider:
- Claude via Vertex AI: GCP project ID, region, and ADC credentials (
gcloud auth application-default login) - Gemini: API key from Google AI Studio
- OpenAI: API key from OpenAI
make build-opencodeThe build script will prompt for:
- Registry: Container registry (e.g.,
docker.io,ghcr.io,zot.paxlab.cc) - Image Tag: Version tag (default:
latest)
These defaults are saved to .push-defaults (gitignored) for future builds.
Copy the secret template and fill in the credentials for at least one provider. OpenAI-only runs need only OPENAI_API_KEY:
cp k8s/secrets/opencode-secret.yaml.template k8s/secrets/opencode-secret.yaml
make create-opencode-secretFor an OpenAI-only setup, leave the Google/Vertex fields blank and set the OpenAI key:
stringData:
OPENAI_API_KEY: "sk-your-key"For Vertex AI, get GCP credentials with:
gcloud auth application-default loginCREDS_FILE defaults to ~/.config/gcloud/application_default_credentials.json. Use make create-opencode-secret CREDS_FILE=<path> to specify another ADC file.
Locally with Podman:
make deploy-podman-opencodeThe image is run locally with the Podman targets below.
Starts OpenCode directly in your terminal:
make deploy-podman-opencodeStarts OpenCode as a background server on localhost:4096:
make serve-podman-opencodeThen connect from your local machine:
opencode # TUI connected to the server
# or open http://localhost:4096 in a browserStop the server with:
podman stop opencodemake build-opencode # Build the OpenCode image
make build # Same targetPush images to registry (reads defaults from .push-defaults):
make push-opencode # Push the OpenCode image
make push # Same target
make publish-opencode # Build and push without prompts
make publish # Same targetcp k8s/secrets/opencode-secret.yaml.template k8s/secrets/opencode-secret.yaml
# Fill in the required values and any optional provider keys, then run:
make create-opencode-secretSecrets are stored as gitignored YAML files in k8s/secrets/.
Podman (local container runtime):
make deploy-podman-opencode
make redeploy-podman-opencode
make resume-podman-opencode
make serve-podman-opencode
make attach-podman-opencodemake helpOpenCode is configured to show Claude, Gemini, and OpenAI models. Select a provider when running a prompt:
# Claude via Vertex AI (interactive TUI — select model in UI)
opencode
# Non-interactive with a specific model
opencode run --model google-vertex-anthropic/claude-sonnet-4-20250514 "explain this code"
opencode run --model google/gemini-2.5-pro "refactor this function"
opencode run --model openai/gpt-4o "write tests for this function"
# List available models
opencode modelsInteractive (inside the container TUI):
Ctrl+A— open session picker to browse and resume previous sessions/sessionsslash command — same as above
CLI:
opencode session list # list all sessions (ID, title, timestamp)
opencode session list --format json # JSON output for scripting
opencode -c # resume last session
opencode -s <session-id> # resume a specific session
opencode run -c "follow-up prompt" # non-interactive continuation of last session.
├── Makefile # Build, push, and Podman targets
├── .push-defaults # Session defaults (gitignored)
├── README.md # This file
├── plans/ # Implementation plan documents
├── containerfiles/
│ ├── Containerfile.agents # OpenCode image definition (shared base + runtimes)
│ └── opencode.json # Provider allowlist (Claude + Gemini + OpenAI)
├── k8s/
│ ├── opencode.yaml # OpenCode Pod template
│ └── secrets/
│ ├── opencode-secret.yaml.template # Template (committed)
│ └── opencode-secret.yaml # Generated (gitignored)
└── scripts/
├── build.sh # Build image with registry/tag prompts
├── push.sh # Push image to registry
├── podman-run.sh # Run container locally
└── create-secrets.sh # Generate secret YAML
Session defaults saved after each build (gitignored):
REGISTRY=zot.paxlab.cc
IMAGE_TAG=0.2
IMAGE_PULL_SECRET=zot-pull-secret
NAMESPACE=agent-coordinator
Edit or delete to reset defaults.
# 1. Update all dependency versions in the Makefile (opencode, go, python, etc.)
make update-deps
# 2. Bump the image tag (stored in ../agent-swarm/.push-defaults)
make set-image-tag IMAGE_TAG=0.x.y
# 3. Build and push the image to the registry
make publish NOPROMPT=1
# 4. Update AGENT_IMAGE_OPENCODE in ../agent-swarm/.env to match the new tagBaked into the image at /sandbox/opencode.json. Restricts available providers to:
{
"enabled_providers": ["google-vertex-anthropic", "google", "openai"]
}google-vertex-anthropic— Claude via Google Vertex AI (GCP credentials)google— Gemini via Google API keyopenai— OpenAI viaOPENAI_API_KEY
The image sets:
GOOGLE_APPLICATION_CREDENTIALS=/app/gcloud/credentials.json(path for Vertex AI creds)DEVCONTAINER=trueEDITOR=nano
At runtime, provider-specific env vars are injected from the secret:
GOOGLE_CLOUD_PROJECT— GCP project ID (Vertex AI, optional)VERTEX_LOCATION— GCP region (Vertex AI, optional)GOOGLE_API_KEY— Gemini API key (optional)OPENAI_API_KEY— OpenAI API key (optional)
At least one provider must be configured. OpenAI-only runs do not require Google credentials or an ADC file.
# 1. Build the image
make build-opencode
# 2. Create secret (one-time setup)
make create-opencode-secret
# 3. Push to the registry
make push-opencode# 1. Create the secret from the template
cp k8s/secrets/opencode-secret.yaml.template k8s/secrets/opencode-secret.yaml
# Fill in the provider credentials, including OPENAI_API_KEY when needed.
make create-opencode-secret
# 2. Build image
make build-opencode
# 3. Run container
make deploy-podman-opencode- Secrets are gitignored: Never commit
k8s/secrets/*.yaml(non-template files) - Credentials at runtime: Mounted via K8s secrets or Podman secret store
- Non-root user: Images run as
nodeuser (non-root for security) - Template files:
.yaml.templatefiles are committed; generated.yamlfiles are not
Build fails with permission denied:
podman versionContainer exits immediately:
- Check logs:
podman logs opencode - Verify secrets are mounted correctly
Secrets not found:
- Verify secret YAML was created:
ls -la k8s/secrets/*.yaml - Regenerate if needed:
make create-opencode-secret
No models listed / auth errors:
- Verify the selected provider's API key is set in
k8s/secrets/opencode-secret.yaml - For Vertex AI, verify
GOOGLE_CLOUD_PROJECT,VERTEX_LOCATION, and the ADC file - Run
opencode modelsinside the container to diagnose provider connectivity
To modify the image definition, edit:
containerfiles/Containerfile.agents— image build definitioncontainerfiles/opencode.json— provider allowlist
The build passes pinned toolchain versions as build arguments and targets the opencode stage.
See LICENSE file for details.