Skip to content

Repository files navigation

🌍 English | Español | Français | Italiano | Português | Deutsch | Русский | हिन्दी | 中文 | 日本語 | 한국어

HolyCode HolyCode

HolyCode Banner

License: MITDocker PullsFull ImageGitHub StarsTwitter FollowPayPalBuy Me A CoffeeWebsiteGitHub ReleaseIssuesContributors

One container. Every tool. Any provider.

Stop maintaining. Start building. A hosted AI workstation. Always-on Linux box. holycode.cloud

OpenCode running in a container with everything already installed. 50+ dev tools, 10+ AI providers, a sandboxed headless browser, persistent state, and Paperclip on top. Drop it on any machine and pick up exactly where you left off.

Hermes remains temporarily unbundled. Its current releases pin vulnerable dependencies. HolyCode leaves /home/opencode/.hermes untouched so you can restore the service when upstream publishes compatible fixed pins.

Paperclip turns HolyCode into an agent board. You get a dashboard on port 3100 where you create a company, hire OpenCode-backed workers, wake them on heartbeat, and manage agent work from a real UI instead of hand-rolling scripts around opencode run.

Works with your Claude subscription. Enable the Claude Auth plugin and use your existing Claude Max/Pro plan. No separate API key needed.

Bring your own multi-agent plugin for now. HolyCode-managed oh-my-openagent installation remains suspended while its current dependency tree retains unresolved security findings. The first flag-free start disables the old managed plugin entry but keeps its settings, skills, and package cache.

You were going to spend an hour getting your environment back. Or you could just docker compose up and get a coding workstation and an agent board in one shot.


What is this?

You know the drill. You get your dev environment exactly right. Then you switch machines. Or rebuild a container. Or your system decides today is the day it dies.

Suddenly you're reinstalling tools. Hunting down config files. Re-entering API keys. Wondering why ripgrep isn't on PATH anymore. Figuring out why Chromium won't launch because Docker gives containers 64MB of shared memory. Then Xvfb isn't configured. Then the UID inside the container doesn't match your host and everything is permission denied.

HolyCode is the container I built after solving every single one of those problems.

It wraps OpenCode, an AI coding agent with a built-in web UI. All your settings, sessions, MCP configs, plugins, and tool history live in a bind mount outside the container. Rebuild, update, or move to a new machine. Your state comes right back.

It's the same idea as HolyClaude but wrapping OpenCode instead of Claude Code. And here's the thing: OpenCode isn't locked to one provider. Point it at Anthropic, OpenAI, Google Gemini, Groq, AWS Bedrock, or Azure OpenAI. Same container, your choice of model.

50+ dev tools, two language runtimes, a sandboxed headless browser stack, process supervision, and an optional Paperclip agent board. All wired up, all ready on first boot. I've been running this on my own server. Every bug has been hit, diagnosed, and fixed.

You pull it. You run it. You open your browser. You build.


Table of Contents

Section
1Quick Start
2HolyCode Cloud (Live)
3Platform Support
4Why HolyCode
5Provider Support
6Docker Compose - Quick
7Docker Compose - Full
8Podman
9Environment Variables
10What's Inside
11Bundled Services
12Architecture
13CLI Usage
14Data and Persistence
15Permissions
16Upgrading
17Troubleshooting
18Building Locally
19Contributing
20Support
21License

🚀 Quick Start

Step 1. Pull the image.

docker pull coderluii/holycode:latest

Step 2. Create a docker-compose.yaml.

The Compose file uses HolyCode's Chromium seccomp profile. If you are not running from a clone of this repository, download the release copy first:

mkdir -p config
curl -fsSLo config/chromium-seccomp.json \
https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json
services:
holycode:
image: coderluii/holycode:latestcontainer_name: holycoderestart: unless-stoppedshm_size: 2gsecurity_opt:
- seccomp=./config/chromium-seccomp.jsonports:
- "4096:4096"volumes:
- ./data/opencode:/home/opencode
- ./local-cache/opencode:/home/opencode/.cache/opencode
- ./workspace:/workspaceenvironment:
- PUID=1000
- PGID=1000
- ANTHROPIC_API_KEY=your-key-here

In that example, /home/opencode is the fixed path inside the container. On the host, ./data/opencode and ./local-cache/opencode are just example bind-mount paths relative to the folder containing your docker-compose.yaml. You can replace them with any host paths you want.

Step 3. Start it.

docker compose up -d

Open http://localhost:4096. You're in.

The shipped docker-compose.yaml uses ${ANTHROPIC_API_KEY} syntax which reads from your shell environment or a .env file. Copy .env.example to .env and fill in your API key.

./data/opencode is only an example host path. If your compose file lives at /opt/holycode, that same bind mount becomes /opt/holycode/data/opencode on the host.

Keep ./local-cache/opencode on local disk. If this project folder lives on NAS/CIFS/SMB storage, change that cache mount to an absolute local host path instead.

back to top


☁ HolyCode Cloud (Live)

HolyCode Cloud is a hosted AI workstation with an always-on Linux box.

What you get with Cloud:

  • Zero setup. No Docker, no config files, no terminal commands.
  • Works on any device. Laptop, tablet, phone. Open a browser and go.
  • Tagged releases refresh OpenCode and the tool pins for you.
  • Your state follows you. Sessions, settings, MCP configs saved between uses.

Open HolyCode Cloud

back to top


💻 Platform Support

PlatformArchitectureStatus
Linuxamd64Supported
Linuxarm64Supported
macOS (Docker Desktop)amd64 / arm64Supported
Windows (WSL2)amd64Supported

back to top


⚡ Why HolyCode

I built this because I was tired of re-doing the same setup every time. Installing OpenCode, wiring up a headless browser, fixing permission issues, debugging process supervision. Every. Time.

So I made a container that does all of it. And then I hit every possible bug so you don't have to.

HolyCodeDIY
Time to first working sessionUnder 2 minutes30-60 minutes
Chromium + Xvfb headless browserPre-configuredResearch, install, debug yourself
Dev tool suite (ripgrep, fzf, lazygit, etc.)Pre-installedHunt down and install one by one
State persistence across rebuildsAutomatic via bind mountManual bind mounts, easy to misconfigure
UID/GID file permission remappingBuilt-in PUID/PGIDDockerfile chmod hacks
Multi-arch supportamd64 + arm64 out of the boxBuild and push both yourself
Updatesdocker pull + compose upRebuild from scratch, hope nothing breaks

back to top


🤖 Provider Support

OpenCode is provider-agnostic. Set whichever API key you use and you're done.

ProviderEnvironment VariableNotes
AnthropicANTHROPIC_API_KEYClaude models
OpenAIOPENAI_API_KEYGPT models
Google GeminiGEMINI_API_KEYGemini models
GroqGROQ_API_KEYFast inference
AWS BedrockAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGIONSet all three
Azure OpenAIAZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_API_VERSIONSet all three
GitHubGITHUB_TOKENGitHub Copilot via OpenAI-compatible endpoint
Vertex AI(configured via OpenCode)Google Vertex AI models
GitHub Models(configured via OpenCode)GitHub-hosted models
Ollama(configured via OpenCode)Local models via Ollama

You only need to set keys for providers you actually use. Everything else is optional and ignored.

Vertex AI, GitHub Models, and Ollama are configured through OpenCode's provider system. Run opencode providers login inside the container.

back to top


📋 Docker Compose - Quick

The minimal setup. Copy, fill in your key, run.

services:
holycode:
image: coderluii/holycode:latestcontainer_name: holycoderestart: unless-stoppedshm_size: 2g# Required for Chromium stabilityports:
- "4096:4096"# OpenCode web UIvolumes:
- ./data/opencode:/home/opencode
- ./local-cache/opencode:/home/opencode/.cache/opencode
- ./workspace:/workspace # Your project filesenvironment:
- PUID=1000
- PGID=1000
- ANTHROPIC_API_KEY=your-key-here # Or swap for any provider key

back to top


📄 Docker Compose - Full

Every option documented. Copy to docker-compose.yaml and uncomment what you need.

# HolyCode - Full Configuration Reference# Copy this file to docker-compose.yaml and customize.# All options documented. Uncomment what you need.services:
holycode:
image: coderluii/holycode:latestcontainer_name: holycoderestart: unless-stoppedshm_size: 2gports:
- "4096:4096"# OpenCode web UIvolumes:
# --- Main HolyCode data ---# Pick any host path you want here. This path maps to /home/opencode in the container.# It can live on local disk or network storage.
- ./data/opencode:/home/opencode# --- Cache path ---# Keep this one on LOCAL disk for plugin/cache reliability.# If your main data path lives on NAS/CIFS/SMB, make this a separate local path.
- ./local-cache/opencode:/home/opencode/.cache/opencode# --- Workspace ---
- ./workspace:/workspace # Your project filesenvironment:
# --- Container user ---
- PUID=1000 # Match your host UID for file permissions
- PGID=1000 # Match your host GID for file permissions# --- Git identity (used on first boot) ---# - GIT_USER_NAME=Your Name# - GIT_USER_EMAIL=you@example.com# --- AI provider API keys (add the ones you use) ---
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}# - OPENAI_API_KEY=${OPENAI_API_KEY:-}# - GEMINI_API_KEY=${GEMINI_API_KEY:-}# - GROQ_API_KEY=${GROQ_API_KEY:-}# - GITHUB_TOKEN=${GITHUB_TOKEN:-}# --- AWS Bedrock (uncomment all 3 for Bedrock) ---# - AWS_ACCESS_KEY_ID=# - AWS_SECRET_ACCESS_KEY=# - AWS_REGION=us-east-1# --- Azure OpenAI (uncomment all 3 for Azure) ---# - AZURE_OPENAI_ENDPOINT=# - AZURE_OPENAI_API_KEY=# - AZURE_OPENAI_API_VERSION=# --- OpenCode behavior (set by default in image, override if needed) ---# - OPENCODE_DISABLE_AUTOUPDATE=true# - OPENCODE_DISABLE_TERMINAL_TITLE=true# - OPENCODE_MODEL=claude-sonnet-4-6# - OPENCODE_PERMISSION=auto# - OPENCODE_DISABLE_LSP_DOWNLOAD=true# - OPENCODE_DISABLE_AUTOCOMPACT=true# - OPENCODE_ENABLE_EXA=true# --- Web UI Security (basic auth for opencode web) ---# - OPENCODE_SERVER_PASSWORD=your-password# - OPENCODE_SERVER_USERNAME=opencode# --- Claude Auth (use Claude subscription instead of API key) ---# Reads credentials from ./data/opencode/.claude/.credentials.json# NOTE: May violate Anthropic TOS. Use at your own risk.# Toggle on/off with docker compose down && up -d# - ENABLE_CLAUDE_AUTH=true# --- Legacy oh-my-openagent flag ---# Managed installation is unavailable in v1.1.4. Existing config is preserved.# Remove this flag before upgrading; true stops with a migration message.# - ENABLE_OH_MY_OPENAGENT=true

CLIProxyAPI remains supported as an external OpenAI-compatible endpoint. HolyCode does not bundle its Docker sidecar until its release binaries have verifiable compiler provenance and pass govulncheck. Set CLIPROXYAPI_BASE_URL to an endpoint that the HolyCode container can reach; this does not replace Claude Auth.

back to top


🐳 Podman

Prefer Podman? HolyCode uses the same container image there too. The Podman guide covers the minimal podman run setup, env-file usage, SELinux labels, rootless permissions, and update/recreate behavior.

Read the Podman guide

back to top


🔧 Environment Variables

VariableDefaultPurpose
PUID1000Container user UID, match your host for correct file ownership
PGID1000Container user GID, match your host for correct file ownership
GIT_USER_NAMEHolyCode UserGit identity configured on first boot
GIT_USER_EMAILnoreply@holycode.localGit identity configured on first boot
ANTHROPIC_API_KEY(none)Anthropic Claude
OPENAI_API_KEY(none)OpenAI GPT models
GEMINI_API_KEY(none)Google Gemini
GROQ_API_KEY(none)Groq fast inference
GITHUB_TOKEN(none)GitHub CLI auth and Copilot
AWS_ACCESS_KEY_ID(none)AWS Bedrock - set all three AWS vars
AWS_SECRET_ACCESS_KEY(none)AWS Bedrock
AWS_REGION(none)AWS Bedrock region (e.g. us-east-1)
AZURE_OPENAI_ENDPOINT(none)Azure OpenAI - set all three Azure vars
AZURE_OPENAI_API_KEY(none)Azure OpenAI
AZURE_OPENAI_API_VERSION(none)Azure OpenAI API version
OPENCODE_DISABLE_AUTOUPDATEtruePrevent OpenCode from self-updating inside the container (does not affect plugins)
OPENCODE_DISABLE_TERMINAL_TITLEtruePrevent OpenCode from changing the terminal title
OPENCODE_MODEL(none)Override the default model
OPENCODE_PERMISSION(none)Set to auto to skip permission prompts
OPENCODE_DISABLE_LSP_DOWNLOAD(none)Disable automatic LSP server downloads
OPENCODE_DISABLE_AUTOCOMPACT(none)Disable automatic context compaction
OPENCODE_ENABLE_EXA(none)Enable Exa web search integration
OPENCODE_SERVER_PASSWORD(none)Protect the web UI with basic auth
OPENCODE_SERVER_USERNAMEopencodeUsername for web UI basic auth
ENABLE_CLAUDE_AUTH(none)Set to true to use Claude subscription instead of API key
ENABLE_OH_MY_OPENAGENT(none)Legacy flag; true stops v1.1.4 because managed installation is suspended
ENABLE_PAPERCLIP(none)Set to true to start the Paperclip dashboard and agent board
PAPERCLIP_PORT3100Override the container port used by Paperclip
PAPERCLIP_INSTANCE_IDdefaultLocal Paperclip instance name for isolated state
PAPERCLIP_DEPLOYMENT_MODEauthenticatedDocker-safe Paperclip startup mode; HolyCode defaults this away from local_trusted
PAPERCLIP_BINDlanPaperclip reachability preset used on first boot; lan binds inside Docker on 0.0.0.0
PAPERCLIP_ALLOWED_HOSTNAMES(none)Comma-separated Paperclip remote hostnames/IPs to allow; use hostname/IP only, no scheme or port
ENABLE_HERMES(none)Legacy flag; true stops v1.1.4 with a migration message while bundled Hermes is unavailable
CLIPROXYAPI_ENABLED(none)Set to true to add the optional OpenCode cliproxyapi provider
CLIPROXYAPI_BASE_URLhttp://cliproxyapi:8317/v1Externally managed CLIProxyAPI base URL reachable from the HolyCode container
CLIPROXYAPI_API_KEY(none)Optional API key for CLIProxyAPI, stored only as an OpenCode env reference when set
CLIPROXYAPI_MODEL(none)Optional primary model key exposed as cliproxyapi/<model>
CLIPROXYAPI_SMALL_MODEL(none)Optional smaller/faster model key exposed as cliproxyapi/<model>
HOLYCODE_PLUGIN_UPDATEmanualPlugin update mode: manual (install if missing and keep user versions) or auto (sync declared pins on boot)

The supported ENABLE_CLAUDE_AUTH toggle takes effect on container restart. Set the env var and run docker compose down && docker compose up -d.

HOLYCODE_PLUGIN_UPDATE controls supported plugin package updates. manual (default) installs Claude Auth only if it is missing and keeps a user-selected version. auto syncs the image's declared Claude Auth pin on boot. This is separate from OPENCODE_DISABLE_AUTOUPDATE, which only affects OpenCode itself.

HolyCode-managed oh-my-openagent installation is unavailable in v1.1.4. If an older deployment still sets ENABLE_OH_MY_OPENAGENT=true, startup stops without changing plugin state. After you remove the flag, the first successful start removes the old active entry from opencode.json and tui.json, records its original package spec in .holycode-oh-my-openagent-migrated-v1.1.4, and keeps the plugin settings, skills, and package cache. Add the plugin back manually only if you accept its current upstream dependency risk; HolyCode then leaves that user-managed entry alone.

ENABLE_PAPERCLIP=true starts Paperclip on port 3100 inside the container. Open the dashboard, create a company, then hire OpenCode-backed agents there. Paperclip persists under ~/.paperclip automatically.

Paperclip runs with HOME=/home/opencode and XDG paths under /home/opencode, matching the OpenCode web service. That keeps the OpenCode adapter on /home/opencode/.config/opencode instead of /root/.config/opencode.

HolyCode starts Paperclip with PAPERCLIP_DEPLOYMENT_MODE=authenticated and PAPERCLIP_BIND=lan by default. That keeps authentication on while allowing Docker port publishing through 0.0.0.0.

PAPERCLIP_ALLOWED_HOSTNAMES lets Paperclip accept listed LAN/private hostnames or IPs. Use comma-separated hostname/IP values only, without http://, https://, or ports. Restart the container after changing it. The hostname guard and Paperclip authentication stay enabled.

Bundled Hermes remains unavailable in v1.1.4 because its current releases require vulnerable dependency pins. If an older deployment still sets ENABLE_HERMES=true, startup stops with a migration message instead of silently ignoring the flag. Your /home/opencode/.hermes data is not changed.

CLIPROXYAPI_ENABLED=true adds a separate OpenCode provider named cliproxyapi. It does not change ENABLE_CLAUDE_AUTH, does not touch /home/opencode/.claude, and does not set global ANTHROPIC_* proxy variables. Set CLIPROXYAPI_BASE_URL to your externally managed service and keep its credentials and network exposure outside the HolyCode container.

GIT_USER_NAME and GIT_USER_EMAIL are only applied on first boot. To re-apply, delete the sentinel file and restart: docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped then docker compose restart.

back to top


📦 What's Inside

Core tools
ToolPurpose
gitVersion control
ripgrepFast file content search
fdFast file finder
fzfFuzzy finder
batCat with syntax highlighting
ezaModern ls replacement
lazygitTerminal git UI
deltaBetter git diffs
ghGitHub CLI
htopProcess monitor
tarArchive creation and extraction
treeDirectory tree visualization
lessPaged file viewer
vimTerminal text editor
tmuxTerminal multiplexer
Language runtimes
RuntimeVersion
Node.js24.18.0 (LTS)
npm12.0.2
Python3.13 (Trixie)
pipBundled with Python 3.13
v1.1.4 release pins
ComponentVersion
OpenCode1.18.9
Paperclip2026.722.0 with reviewed Undici 6.28.0 replacement
HermesBundled service temporarily removed; existing .hermes data is preserved
CLIProxyAPIBundled sidecar removed; external endpoints remain supported
s6-overlay3.2.3.2
eza0.23.5
fzf0.74.1
lazygit0.63.1
pnpm11.18.0
Vite8.1.5
ESLint10.8.0
Prettier3.9.6
Wrangler4.115.0; legacy service environments are not supported
Prisma7.9.1
Lighthouse13.4.1
Netlify CLI and serveRemoved
Hermes, Vercel, sharp-cli, concurrently, LHCIRemoved because their current dependency trees contain unresolved or fixable security findings
tqdm4.70.0
FastAPI / Uvicorn0.141.1 / 0.52.0
Claude stable2.1.220
tsx4.23.1
TypeScript6.0.3, held until TypeScript 7 exposes the stable toolchain APIs this image needs
NumPy2.5.1 on Python 3.13
json-server0.17.4, held on the stable release instead of the 1.0 beta
opencode-claude-auth default2.1.5, integrity-verified and installed from the image
oh-my-openagentHolyCode-managed installation suspended; legacy active entry disabled once while settings, skills, and cache remain

Release assets use digests, checksums, and action SHAs for hardening. npm lifecycle scripts are installed disabled, then their exact package version, integrity, architecture, and script body are validated before the approved scripts run. HolyCode also publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans, but it does not claim universal freshness or that future rebuilds will retain the same scanner result.

The dated adoption, hold, removal, exception, and scanner decisions are in the v1.1.4 dependency audit.

Dev tools
ToolPurpose
curlHTTP requests
wgetFile downloads
jqJSON processing
unzip / zipArchive tools
sshRemote access
build-essential + pkg-configNative npm addon compilation
python3-venvPython virtual environments
procpsProcess tools: ps, top
iproute2Network tools: ip, ss
lsofOpen file diagnostics
OpenSSLCrypto and cert tools (via base image)
Browser stack
ComponentPurpose
ChromiumHeadless browser engine
XvfbVirtual framebuffer display server
PlaywrightBrowser automation framework

The browser stack runs headless out of the box. No display server, no GPU, no extra config needed. Playwright and Puppeteer scripts work as expected.

Includes Liberation, DejaVu, Noto, and Noto Color Emoji fonts for correct page rendering and screenshots.

Bundled services
ServicePurpose
PaperclipLocal agent board that hires OpenCode workers and wakes them on heartbeat
CLIProxyAPI integrationExternal OpenAI-compatible account/model routing through CLIPROXYAPI_*
Claude Code CLIInstalled for Claude subscription auth flows via ENABLE_CLAUDE_AUTH
Process management
ComponentPurpose
s6-overlay v3Process supervisor and init system
Custom entrypointUID/GID remapping, git setup, bootstrap

s6-overlay supervises OpenCode and Xvfb. If a process crashes, it restarts automatically. Container restart policies stay clean because the supervisor handles it internally.

back to top


🧩 Bundled Services

HolyCode ships with optional Paperclip on top of OpenCode, plus integration for an externally managed CLIProxyAPI endpoint. You do not need either one to use the container.

  • Paperclip is for when you want a board, a workflow, and actual agent management instead of just one-off prompts.
  • CLIProxyAPI integration is for when you already manage an OpenAI-compatible endpoint and want OpenCode to use it as a separate provider.

Hermes Agent

Hermes remains temporarily unbundled in v1.1.4. The current Hermes release line pins vulnerable dependencies and adds unresolved findings through its optional dependency set. HolyCode removes the runtime and service instead of shipping those packages.

Existing state under /home/opencode/.hermes remains untouched. Remove ENABLE_HERMES=true from older Compose deployments before starting v1.1.4. The container stops with a clear migration message when that legacy flag remains set, so the missing service cannot be mistaken for a successful start.

Paperclip

Paperclip is the "agent board" option. It gives you a local dashboard on port 3100 where you create a company, hire agents, and let those agents wake up on schedule. Under the hood it spawns opencode run processes, so the workers are still HolyCode.

Why that matters:

  • A real control surface. You stop treating agents like random shell commands and start treating them like a team with roles, tasks, and wake cycles.
  • OpenCode-backed workers, not a toy layer. The board is Paperclip. The actual worker execution is still HolyCode doing real coding work.
  • Faster delegation experiments. Create a company, assign work, and see how an agent workflow feels without building the orchestration stack yourself.
  • Persistent board state. Data, config, storage, and embedded Postgres all live under ~/.paperclip.

Paperclip is the control room. Turn it on when you want to manage agent work, not just launch it.

Turn it on with:

environment:
- ENABLE_PAPERCLIP=true
- PAPERCLIP_PORT=3100
- PAPERCLIP_DEPLOYMENT_MODE=authenticated
- PAPERCLIP_BIND=lan
- PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local

Paperclip state lives under /home/opencode/.paperclip. HolyCode bootstraps it in authenticated mode with the lan bind preset so Docker port publishing works cleanly. Paperclip also runs with /home/opencode as its home and keeps OpenCode config/cache/state paths under that same directory, so OpenCode-backed employees see the same persisted config as the OpenCode web UI. Paperclip now ships its Skills catalog through the package set HolyCode installs, so the Skills page loads instead of failing on GET /api/skills/catalog. Open the dashboard, set up your company, and hire OpenCode-backed employees from there.

When opening Paperclip from another machine, set PAPERCLIP_ALLOWED_HOSTNAMES to the hostname or IP from the browser URL, without http://, https://, or :3100. Use commas for multiple values and restart the container after changes. This only allowlists those private hostnames; it does not make Paperclip public or disable authentication.

CLIProxyAPI

CLIProxyAPI is the "model router" option. HolyCode can add a separate OpenCode provider named cliproxyapi that points at an externally managed OpenAI-compatible endpoint.

Why that matters:

  • One provider surface. OpenCode can use cliproxyapi/<model> while CLIProxyAPI handles the account/model routing behind it.
  • Isolated from Claude Auth. This does not replace ENABLE_CLAUDE_AUTH, does not touch /home/opencode/.claude, and does not set global Anthropic proxy variables.
  • No bundled unverified service. CLIProxyAPI stays external-only until its published binaries have verifiable compiler provenance and pass govulncheck.
  • Separate ownership. You manage CLIProxyAPI config, auth, updates, and network exposure outside HolyCode.

Turn it on with:

environment:
- CLIPROXYAPI_ENABLED=true
- CLIPROXYAPI_BASE_URL=http://your-cliproxy-host:8317/v1
- CLIPROXYAPI_API_KEY=
- CLIPROXYAPI_MODEL=your-model-id

Make sure the endpoint is reachable from the HolyCode container before restarting. Put CLIProxyAPI API keys and OAuth state in the external service; do not reuse /home/opencode/.claude.

back to top


🏗 Architecture

graph TD
A[docker compose up -d] --> B[entrypoint.sh]
B --> C[UID/GID Remap]
C --> D[Plugin and Service Toggles]
D --> E{First Boot?}
E -->|Yes| F[bootstrap.sh]
E -->|No| G[s6-overlay /init]
F --> G
G --> H[Xvfb :99]
G --> I[opencode web :4096]
G --> R[Paperclip UI :3100]
V[External CLIProxyAPI endpoint] --> U[cliproxyapi provider]
I --> J[Web UI]
J --> K[Your Browser]
I --> L[CLI Access]
L --> M[docker exec -it holycode bash]
M --> N[opencode TUI]
M --> O[opencode run 'message']
M --> P[opencode attach localhost:4096]
R --> T[Agent board and CEO invite]
I --> U
Loading

The entrypoint handles user remapping, plugin toggles, Paperclip startup, CLIProxyAPI provider injection, and first-boot setup. s6-overlay supervises Xvfb, the OpenCode web server, and Paperclip when enabled. CLIProxyAPI, when configured, is external. Access the OpenCode web UI at port 4096 or Paperclip on 3100.

back to top


💻 CLI Usage

The web UI at port 4096 is the primary interface. But you can also use OpenCode directly from the command line inside the container.

Interactive TUI

docker exec -it holycode bash
opencode

This opens OpenCode's full terminal UI with all the same features as the web version.

One-shot commands

Run a single prompt without entering the TUI:

docker exec -it holycode bash -c "opencode run 'explain this codebase'"

Attach to the running server

Connect a local TUI session to the already-running OpenCode web server:

docker exec -it holycode bash -c "opencode attach http://localhost:4096"

This shares the same session as the web UI. Changes in one appear in the other.

Provider management

List and configure AI providers from inside the container:

docker exec -it holycode bash -c "opencode providers list"
docker exec -it holycode bash -c "opencode providers login"

Useful commands

CommandWhat it does
opencodeLaunch the TUI
opencode run 'message'One-shot prompt
opencode attach <url>Attach TUI to running server
opencode web --port 4096Start web server (already running via s6)
opencode serveHeadless API server
opencode providers listShow configured providers
opencode providers loginAdd or switch provider
bunx oh-my-opencode refresh-model-capabilitiesRefresh provider/model capability cache after provider changes
opencode modelsList available models
opencode models <provider>List models for a specific provider
opencode statsShow token usage and costs
opencode session listList past sessions
opencode export <sessionID>Export session as JSON
opencode plugin <module>Install a plugin
opencode upgradeUpgrade OpenCode (disabled by default in container)

back to top


💾 Data and Persistence

Most OpenCode state lives under /home/opencode inside the container. On the host, that data appears wherever you bind-mount /home/opencode. In the default examples below, the host path is ./data/opencode, but you can replace it with any path you want.

Plugin cache is mounted separately at ./local-cache/opencode by default so you can keep that cache path on local disk even if your main data path is somewhere else.

Host PathContainer PathWhat's in it
./data/opencode/.config/opencode*/home/opencode/.config/opencodeSettings, agents, MCP configs, themes, plugins
./data/opencode/.local/share/opencode*/home/opencode/.local/share/opencodeSQLite sessions database, MCP OAuth tokens
./data/opencode/.local/state/opencode*/home/opencode/.local/state/opencodeFrecency data, model cache, key-value store
./local-cache/opencode/home/opencode/.cache/opencodePlugin node_modules, auto-installed dependencies

* These ./data/opencode/... paths are example host paths from the sample compose file. If you bind /home/opencode to a different host path, the same subdirectories will appear there instead.

Rebuild the container anytime. Run docker compose pull && docker compose up -d and your sessions, settings, and configs come back automatically.

The Dockerfile pins direct npm, PyPI, and GitHub-release versions. Binary release assets use checksums, container bases use digests, and GitHub Actions use commit SHAs. Claude Code is installed from @anthropic-ai/claude-code@2.1.220. npm lifecycle scripts are disabled during installation. HolyCode validates each script package's version, integrity, architecture, and script body before running only the approved OpenCode, Claude Code, and Paperclip embedded PostgreSQL steps. The supported Claude Auth plugin is included as an integrity-verified offline payload. Python packages use a hash-locked requirements file, and new virtual environments can bootstrap audited packaging tools from the image's offline seed. Debian packages still resolve from current Trixie repositories at build time, so a later rebuild is not guaranteed to be byte-for-byte identical. User-installed plugins remain outside the image SBOM. Each release publishes per-platform SBOM and provenance attestations and runs per-platform vulnerability scans without promising universal freshness or zero total findings.

SQLite WAL note. The sessions database uses Write-Ahead Logging. Don't copy the .db file while the container is running. Stop the container first if you need to back up or migrate the database file.

Network storage note. If ./data/opencode is on a CIFS/SMB network mount (NAS, Synology, TrueNAS), you need two mount options:

  • nobrl — SQLite WAL mode requires this (byte-range locking workaround)
  • mfsymlinks — plugin installation requires this (symlink support for node_modules)

Keep ./local-cache/opencode on local disk. If your whole HolyCode folder lives on network storage, change that cache mount to an absolute local host path such as /var/lib/holycode-cache/opencode:/home/opencode/.cache/opencode.

See the Troubleshooting section below.

back to top


🔐 Permissions

HolyCode uses PUID and PGID to remap the internal container user to match your host user. This means files written to ./workspace are owned by you, not by root.

Find your IDs on Linux and macOS:

id -u # PUID
id -g # PGID

On most systems this is 1000:1000. On macOS it's often 501:20. Set them in your compose file:

environment:
- PUID=501
- PGID=20

If you skip this, files in your workspace may be owned by root and you'll need sudo to edit them from the host.

back to top


⬆️ Upgrading

Stop the stack and copy ./data, ./local-cache, and ./workspace before upgrading. Migrations can change persisted data, so keep those pre-upgrade copies until the new image has passed your normal workflows.

If you are upgrading from a release before v1.1.3, download the Chromium seccomp profile and add it to the holycode service before recreating the container:

mkdir -p config
curl -fsSLo config/chromium-seccomp.json \
https://raw.githubusercontent.com/CoderLuii/HolyCode/v1.1.3/config/chromium-seccomp.json
security_opt:
- seccomp=./config/chromium-seccomp.json
docker compose stop
# Copy ./data, ./local-cache, and ./workspace with your host backup tool.
docker compose pull
docker compose up -d

v1.1.4 upgrades Paperclip from 2026.707.0 to 2026.722.0 and runs database migrations through 0183. Keep your untouched pre-upgrade copies until onboarding, Skills, agents, connections, and normal provider work all pass.

If you need to roll back, stop the stack, change the Compose image to coderluii/holycode:1.1.3, restore the untouched pre-v1.1.4 copies, and start the stack again. Rollback means restoring those snapshots. It does not reverse Paperclip migrations in place, and you must not point v1.1.3 at data already changed by v1.1.4.

After the checks pass, remove the backup on your own schedule.

back to top


🛠 Troubleshooting

Chromium crashes or browser automation fails

The most common cause is not enough shared memory. Chromium needs at least 1-2 GB of /dev/shm to run reliably.

Make sure your compose file has shm_size: 2g:

services:
holycode:
shm_size: 2g

Without this, Chromium will crash silently or produce broken screenshots.

Permission denied on workspace files

Your PUID and PGID don't match your host user. Find your IDs:

id -u && id -g

Update your compose environment section to match:

environment:
- PUID=1001 # replace with your actual UID
- PGID=1001 # replace with your actual GID

Then recreate the container: docker compose up -d --force-recreate

Port 4096 already in use

Something else on your machine is using port 4096. Remap to a different host port:

ports:
- "4097:4096"# access via http://localhost:4097

Or find and stop the conflicting process:

# Linux / macOS
lsof -i :4096
# Windows
netstat -ano | findstr :4096
Container starts but web UI never loads

Check the container logs:

docker compose logs -f holycode

OpenCode takes a few seconds to initialize. Give it 10-15 seconds after docker compose up -d before opening the browser. If it's still not up, the logs will tell you why.

How is Chromium sandboxed?

HolyCode runs Chromium as the opencode user with Chromium's sandbox enabled. The shipped Compose files apply a constrained seccomp profile that permits the namespace syscalls the sandbox needs without granting SYS_ADMIN or disabling seccomp.

Keep this setting when writing your own Compose file:

security_opt:
- seccomp=./config/chromium-seccomp.json

The profile path is relative to your Compose file. Do not add --no-sandbox, SYS_ADMIN, or seccomp=unconfined as a browser workaround.

SQLite WAL or plugins fail on CIFS/SMB network mounts (NAS)

If your ./data/opencode directory lives on a CIFS/SMB network share (e.g. NAS, Synology, TrueNAS), OpenCode may fail with:

Failed to run the query 'PRAGMA journal_mode = WAL'

OpenCode uses SQLite with Write-Ahead Logging (WAL) for its sessions database. WAL requires byte-range locking, which CIFS/SMB doesn't support by default.

HolyCode detects this at startup and prints a warning with the fix instructions.

Fix: Add nobrl,mfsymlinks to your CIFS mount options in /etc/fstab:

# Before
//192.168.1.100/share /mnt/share cifs credentials=/etc/smbcreds,uid=1000,gid=1000 0 0
# After — add nobrl and mfsymlinks
//192.168.1.100/share /mnt/share cifs credentials=/etc/smbcreds,uid=1000,gid=1000,nobrl,mfsymlinks 0 0

Then remount:

sudo umount /mnt/share
sudo mount /mnt/share

Restart HolyCode: docker compose up -d --force-recreate

If you are using the default HolyCode Compose files, the cache mount is ./local-cache/opencode:/home/opencode/.cache/opencode. Keep that path on local disk. If your entire HolyCode folder lives on network storage, replace it with an absolute local host path.

back to top


🔨 Building Locally

Clone the repo, build the image, swap it into your compose file.

git clone https://github.com/coderluii/holycode.git
cd holycode
docker build -t holycode:local .

Then in your docker-compose.yaml swap the image:

image: holycode:local

back to top


🤝 Contributing

  1. Fork the repo
  2. Create a branch: git checkout -b feature/your-feature
  3. Commit your changes: git commit -m "feat: your feature"
  4. Push: git push origin feature/your-feature
  5. Open a pull request

See CONTRIBUTING.md for full guidelines.

back to top


⭐ Support

If HolyCode saved you from another hour of environment setup, here's how to pay it forward.

back to top


📄 License

MIT License - see LICENSE.

back to top


About

AI coding workstation: OpenCode + Claude subscription support + 30+ tools + headless browser + multi-agent orchestration

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

57 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages