The Mini Rack Saga - Part 5: Offline AI Coding with opencode-offline
We’ve spent four parts building a homelab AI stack — physical rack, Raspberry Pi nodes, an HA Kubernetes cluster, and a fully self-hosted AIML deployment on an M4 Pro Mac mini. In Part 5 we’re putting that stack to work as a coding agent.
We’re installing opencode-offline on a developer laptop and pointing it at LiteLLM over Tailscale. The result is a fully offline AI coding agent — no cloud API keys, no telemetry, no data leaving your network.
And while this started as a homelab project, the pattern here has real-world applicability. I’ve deployed this exact setup in a government cloud environment where strict network controls mean nothing can phone home. If it works there, it works anywhere.
What is opencode-offline? Link to heading
OpenCode is an open source AI coding agent — similar to Claude Code but completely provider-agnostic. It lives in your terminal, understands your codebase, and can read, write, and run code on your behalf.
The problem with upstream OpenCode in an airgapped environment is that it requires internet access for several core functions: the web UI proxies requests to app.opencode.ai, language servers (TypeScript, Python, C++, Rust) are downloaded on first use, and model metadata is fetched from an external service. In a network-isolated environment that means no web interface, no file search, no code intelligence.
The Chetic/opencode-offline fork solves this by bundling everything — ripgrep, LSP servers, the web UI, model definitions — into a self-contained tarball. You build it once on an internet-connected machine, validate it in an isolated container, then transfer it to your airgapped host. After that it runs with OPENCODE_OFFLINE_MODE=true and never needs to reach the internet.
The Pattern: Build Online, Run Offline Link to heading
This is the key architectural insight worth calling out before we get into steps. The workflow is:
Internet-connected machine → build bundle → validate → transfer → airgapped host
This is exactly how airgapped software deployments work in regulated environments — you do your dependency resolution and packaging on a connected build machine, then ship a self-contained artifact across the air gap. opencode-offline is built with this pattern in mind.
Prerequisites Link to heading
- Bun runtime installed on your build machine (bun.sh)
- Docker (optional, for airgapped validation)
- Tailscale connected on your laptop with access to the Mac mini
- LiteLLM running on the Mac mini from Part 4
- A LiteLLM API key for your user
Step 1: Clone the Repo Link to heading
git clone https://github.com/Chetic/opencode-offline.git
cd opencode-offline
git checkout dev
Note: The active branch is
dev, notmain. Make sure you’re on the right branch before proceeding.
Step 2: Install Dependencies Link to heading
bun install
Step 3: Download Offline Dependencies Link to heading
This step bundles ripgrep, LSP servers for TypeScript, Python, C++, and Rust, and the web UI so none of them need to be fetched at runtime:
bun run script/download-offline-deps.ts
This is where the internet connection is actually used — everything gets pulled down and packaged here so the runtime never needs to reach out.
Step 4: Package the Offline Bundle Link to heading
Produces a self-contained tarball with everything needed to run in an airgapped environment:
bun run script/package-offline-bundle.ts
The output is opencode-offline-bundle.tar.gz — roughly 100MB, containing the binary and all dependencies.
Step 5: (Optional) Validate in an Airgapped Container Link to heading
Before transferring to your target host, you can validate the bundle actually works with zero network access. This spins up a network-isolated RHEL9 container and runs the full bundle inside it:
docker compose -f test/offline/docker-compose.yml up --build
This is worth doing especially in regulated environments where you need to prove offline operation before deployment.
Step 6: Configure opencode.json Link to heading
Before transferring, create your opencode.json config. This tells opencode-offline where to find LiteLLM and which model to use.
Place it at one of these locations depending on your preference:
| Location | Scope |
|---|---|
~/.config/opencode/opencode.json | Global (all projects) |
./opencode.json (project root) | Project-specific |
Custom path via OPENCODE_CONFIG env var | Explicit override |
Here’s the config for pointing at LiteLLM on the Mac mini over Tailscale:
{
"model": "litellm/gemma4",
"provider": {
"litellm": {
"npm": "@ai-sdk/openai-compatible",
"name": "LiteLLM Proxy",
"options": {
"baseURL": "http://<your-mac-mini-tailscale-ip>:4000/v1",
"apiKey": "<your-litellm-api-key>"
},
"models": {
"gemma4": {
"name": "Gemma 4",
"limit": {
"context": 128000,
"output": 8192
}
}
}
}
}
}
The model key (
gemma4) must exactly match themodel_nameyou defined in your LiteLLMconfig.yamlin Part 4. If they don’t match, requests will fail silently.
Step 7: Transfer to Your Target Host Link to heading
Copy both files to your target machine:
opencode-offline-bundle.tar.gz
opencode.json
Extract the bundle:
tar -xzf opencode-offline-bundle.tar.gz
Step 8: Run Link to heading
Navigate to your project directory and launch:
./opencode-offline
Switch between agents with Tab:
- build — full access, reads and writes files, runs commands
- plan — read-only, great for exploring a codebase or planning changes before making them
All requests route through LiteLLM on the Mac mini to Ollama running Gemma 4 — entirely within your tailnet, no internet required.
Known Gotchas Link to heading
A few things that can trip you up:
IPv6 vs IPv4 — If opencode can’t connect to your LiteLLM endpoint, it may be resolving the hostname to an IPv6 address. Use an explicit IPv4 address in baseURL rather than a hostname to avoid this.
No trailing slash on baseURL — Use http://host/v1 not http://host/v1/. The trailing slash causes silent connection failures.
Model ID must match exactly — The key in the models block of opencode.json must match what LiteLLM exposes, not what the upstream provider calls it. Double check your LiteLLM config.yaml if requests aren’t routing correctly.
NGINX SSL cipher mismatch — If you’re running NGINX as a reverse proxy in front of LiteLLM, ensure TLS cipher suites are compatible. A mismatch causes silent connection failures that are painful to debug.
Wrapping Up Link to heading
Here’s the complete Mini Rack Saga:
- ✅ Part 1 — Physical rack build
- ✅ Part 2 — Raspberry Pi software setup, NVMe boot, Docker stacks
- ✅ Part 3 — HA RKE2 cluster on Mac minis, Tailscale Operator
- ✅ Part 4 — Self-hosted AIML stack: Ollama, LiteLLM, Open WebUI, Keycloak SSO
- ✅ Part 5 — Offline AI coding with opencode-offline
From physical rack to fully offline AI coding agent — all on your own hardware, all on your own network, with a deployment pattern that works just as well in a government cloud as it does in a homelab.