Build and deploy containerized AI code agents with language-specific tooling.
This repository builds container images for OpenCode — an AI coding agent that supports both Claude (via Vertex AI or direct API) and Gemini as selectable providers. Images are available with Go or Python 3 support and can be deployed locally with Podman or to Kubernetes.
opencode-golang- OpenCode with Go 1.xopencode-python- OpenCode with Python 3
Each image includes:
- Git and essential development tools
- Node.js 20 (slim base)
- OpenCode CLI pre-installed (
opencode-ai) - Provider config restricting to Claude and Gemini models only
- Language runtime (Go or Python)
- Development utilities (fzf, ripgrep, jq, vim, nano, zsh, less)
- Podman or Docker (for building)
- Make (for running build/deploy targets)
- Git
- kubectl (for Kubernetes deployments only)
- API credentials:
- Claude via Vertex AI: GCP project ID, region, and ADC credentials (
gcloud auth application-default login) - Gemini: API key from Google AI Studio
- Claude via Vertex AI: GCP project ID, region, and ADC credentials (
make build-opencode-golangThe 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.
make create-opencode-secret \
PROJECT_ID=your-gcp-project-id \
REGION=us-east5 \
GOOGLE_API_KEY=your-gemini-api-keyGet GCP credentials with:
gcloud auth application-default loginCREDS_FILE defaults to ~/.config/gcloud/application_default_credentials.json.
Locally with Podman:
make deploy-podman-opencode-golangTo Kubernetes:
make deploy-k8s-opencode-golangThe deploy script will prompt for:
- Image Pull Secret (name of existing K8s secret)
- Namespace (K8s namespace to deploy to)
Starts OpenCode directly in your terminal:
make deploy-podman-opencode-golangStarts OpenCode as a background server on localhost:4096:
make serve-podman-opencode-golangThen connect from your local machine:
opencode # TUI connected to the server
# or open http://localhost:4096 in a browserStop the server with:
podman stop opencode-golangAfter deploying to K8s, forward the pod's port to your local machine:
make connect-opencode-golangThen connect at localhost:4096 the same way as above. Press Ctrl+C to stop forwarding.
make build-opencode-golang # OpenCode + Go
make build-opencode-python # OpenCode + Python
make build-all # Both imagesPush images to registry (reads defaults from .push-defaults):
make push-opencode-golang # Push golang image
make push-opencode-python # Push python image
make podman-push # Push both images (also tags :latest)make create-opencode-secret \
PROJECT_ID=my-project \
REGION=us-east5 \
GOOGLE_API_KEY=xyzSecrets are stored as gitignored YAML files in k8s/secrets/.
Podman (local container runtime):
make deploy-podman-opencode-golang
make deploy-podman-opencode-pythonKubernetes:
make deploy-k8s-opencode-golang
make deploy-k8s-opencode-pythonmake helpOpenCode is configured to show only Claude and Gemini 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"
# 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, deploy targets
├── .push-defaults # Session defaults (gitignored)
├── README.md # This file
├── plans/ # Implementation plan documents
├── containerfiles/
│ ├── Containerfile.opencode # OpenCode image definition (shared base + lang layers)
│ └── opencode.json # Provider allowlist (Claude + Gemini only)
├── k8s/
│ ├── opencode-golang.yaml # OpenCode + Go Pod template
│ ├── opencode-python.yaml # OpenCode + Python 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
├── deploy.sh # Deploy to Kubernetes
├── 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.
Baked into the image at /workspace/opencode.json. Restricts available providers to:
{
"enabled_providers": ["google-vertex-anthropic", "google"]
}google-vertex-anthropic— Claude via Google Vertex AI (GCP credentials)google— Gemini via Google API key
The image sets:
GOOGLE_APPLICATION_CREDENTIALS=/app/gcloud/credentials.json(path for Vertex AI creds)DEVCONTAINER=trueEDITOR=nano
At runtime, the following env vars are injected from the secret:
GOOGLE_CLOUD_PROJECT— GCP project ID (Vertex AI)VERTEX_LOCATION— GCP region (Vertex AI)GOOGLE_API_KEY— Gemini API key
Default Kubernetes resource requests/limits:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"Edit K8s YAML files to customize.
# 1. Build all images
make build-all
# 2. Create secret (one-time setup)
make create-opencode-secret \
PROJECT_ID=my-project \
REGION=us-east5 \
GOOGLE_API_KEY=your-gemini-api-key
# 3. Push all to registry
make podman-push
# 4. Deploy to K8s
make deploy-k8s-opencode-golang# 1. Create secret
make create-opencode-secret PROJECT_ID=my-project REGION=us-east5
# 2. Build image
make build-opencode-golang
# 3. Run container
make deploy-podman-opencode-golangAgents authenticate to GitHub via an SSH deploy key scoped to a single repository. Setup requires a one-time GitHub Personal Access Token (PAT) to register the key — the PAT is not stored anywhere.
export GITHUB_PERSONAL_ACCESS_TOKEN=<your-pat>
make setup-github REPO=owner/repoThis generates an ephemeral RSA key pair, stores the private key as a secret, registers the public key as a deploy key on the repo, then wipes the key files. The PAT is used only for this step.
Generate a PAT at github.com/settings/tokens.
| PAT type | Required permission |
|---|---|
| Fine-grained (recommended) | Administration → Read and write (on the target repo) |
| Classic | repo scope (or public_repo for public repos only) |
The PAT does not need any other scopes. It is read from GITHUB_PERSONAL_ACCESS_TOKEN in your environment, with GITHUB_TOKEN as a fallback.
By default, deploy keys are registered with write access (read_only=false). To register as read-only:
make setup-github REPO=owner/repo READ_ONLY=true# Remove completed/failed agent pods from K8s
make clean-agents-k8s
# Remove stopped agent containers from Podman
make clean-agents-podman- 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 versionK8s deployment fails to pull image:
- Verify image push succeeded:
podman images - Check image pull secret exists:
kubectl get secrets -n <namespace> - Verify registry URL in
.push-defaults
Container exits immediately:
- Check logs:
kubectl logs <pod-name> -n <namespace> - For Podman:
podman logs <container-id> - 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 PROJECT_ID=... REGION=... - Ensure namespace matches in K8s deployment
No models listed / auth errors:
- Verify
GOOGLE_CLOUD_PROJECTandVERTEX_LOCATIONare set correctly - Check
GOOGLE_APPLICATION_CREDENTIALSpoints to a valid credentials file - Run
opencode modelsinside the container to diagnose provider connectivity
To modify the image definition, edit:
containerfiles/Containerfile.opencode— image build definitioncontainerfiles/opencode.json— provider allowlist
Build arguments:
LANG— Language variant:golangorpython--target final— Build final stage only
See LICENSE file for details.