hermes-opencode
Docker image that bakes OpenCode CLI into the Hermes Agent container. One container, free AI models, zero SSH tunnels. Extensible recipe for any Debian-based tool you want to bake in.
What’s Included
| Component | Version | Location |
|---|---|---|
| Hermes Agent | latest | Base image (Nous Research) |
| OpenCode CLI | 1.18.4 | /usr/local/bin/opencode |
| Debian OS | 13 (Trixie) | Base image OS |
| SearXNG | latest | Optional (separate container) |
| Ollama | latest | Optional (separate container, local vision models) |
Architecture
Layer Stack (how the image is built)
Layer 0: FROM nousresearch/hermes-agent:latest
└── Debian 13 + Python + Hermes Agent binaries
Layer 1: RUN apt-get install ... (Debian packages)
└── vim, htop, build-essential, python3-pip, git, curl, ...
Layer 2: RUN curl ... | tar -xzf ... (binaries)
└── OpenCode CLI binary
= hermes-opencode:latest (built image)
↓
docker compose up -d
↓
Layer 3: Container runtime (bind mounts, env vars)
= Running container
Each RUN in the Dockerfile creates a layer. Docker caches layers — rebuild is fast if only bottom layers change.
Container Network
┌─────────────────────────────────────────────────────┐
│ hermes-opencode_hermes-net │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ hermes-opencode │ │ searxng │ │
│ │ (Hermes+OpenCode)│ │ (metasearch) │ │
│ │ │ │ │ │
│ │ /usr/local/bin/ │ │ port 8080 │ │
│ │ opencode │ │ │ │
│ └────────┬────────┘ └──────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ ollama │ │
│ │ (local LLM/VLM)│ │
│ │ port 11434 │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────┘
Quick Start
# Fresh deploy (from ext_storage)
rsync -avP /ext_storage/hermes-opencode/ root@YOUR_VPS:/opt/hermes-opencode/
cd /opt/hermes-opencode
mkdir -p workspace searxng && chown -R 10000:10000 workspace searxng
docker compose up -d
# Or use the deploy script
bash /ext_storage/hermes-opencode/deploy.sh <VPS_IP>
Verify
docker exec hermes-opencode-hermes-opencode-1 hermes --version
docker exec hermes-opencode-hermes-opencode-1 opencode --version
# → 1.18.4
Persistence Architecture
This deployment uses bind mounts — data lives directly on the host filesystem and is accessible from both the container and the host.
Bind Mount: ./hermes_data → /opt/data (Hermes config, memory, skills, profiles)
Bind Mount: ./opencode_data → /home/hermes/.opencode (OpenCode sessions)
Bind Mount: ./workspace → /home/hermes/workspace (working files, host-accessible)
| Volume | Type | Persists across restart? | Host-accessible? |
|---|---|---|---|
./hermes_data |
Bind mount | ✅ Yes | ✅ Yes — direct file access |
./opencode_data |
Bind mount | ✅ Yes | ✅ Yes — direct file access |
./workspace |
Bind mount | ✅ Yes | ✅ Yes — direct file access |
./searxng |
Bind mount | ✅ Yes | ✅ Yes — direct file access |
Why bind mounts instead of named volumes?
| Factor | Named Volumes | Bind Mounts |
|---|---|---|
| Host file access | ❌ Via docker volume commands | ✅ Direct file access |
| Backup | docker volume cp |
cp / rsync directly |
| Root required | No | Yes |
| NFS/storage integration | Limited | ✅ Works with NFS |
| Chown permissions | Docker-managed | Manual |
Named volumes are better when:
- Running as a non-root user
- Need portability across different host environments
- Want protection from accidental host directory deletion
Bind mounts are better when:
- Running as root (you have full control)
- Need direct file access from host (backup, edit, inspect)
- Using NFS or other network storage
- Want simple backup with standard tools
This deployment uses bind mounts because it runs as root with NFS backup storage.
Ollama Integration (Image Recognition)
Hermes can use Ollama for local image recognition via HTTP API:
Python (in Hermes) → Ollama API (172.18.0.x:11434) → gemma4:31b-cloud
Example usage:
import urllib.request, json, base64
with open('/path/to/image.jpg', 'rb') as f:
img_b64 = base64.b64encode(f.read()).decode()
payload = json.dumps({
'model': 'gemma4:31b-cloud',
'prompt': '描述这张图片',
'images': [img_b64],
'stream': False
}).encode()
req = urllib.request.Request(
'http://172.18.0.4:11434/api/generate',
data=payload,
headers={'Content-Type': 'application/json'},
method='POST'
)
with urllib.request.urlopen(req, timeout=120) as resp:
result = json.loads(resp.read().decode())
print(result['response'])
GPU Passthrough
GPU access is runtime-only — the Dockerfile doesn’t handle GPU. It’s purely controlled by the compose file and host driver.
HOST machine Container
(nvidia-driver installed) (nvidia runtime)
Physical GPU (PCIe) ←── nvidia driver ──→ /dev/nvidia0, /dev/nvidiactl
↓
container sees GPU
Host-level setup (do once on host)
# 1. Install NVIDIA driver
apt-get update && apt-get install -y nvidia-driver-545 nvidia-smi
# 2. Install nvidia-container-toolkit
distribution=bookworm
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
apt-get update && apt-get install -y nvidia-container-toolkit
# 3. Configure Docker to use nvidia runtime
nvidia-ctk runtime configure --runtime=docker
systemctl restart docker
Compose GPU config
services:
hermes-opencode:
runtime: nvidia # ← maps GPU devices into container
environment:
- NVIDIA_VISIBLE_DEVICES=all
Container-side GPU tools (add to Dockerfile)
RUN apt-get update && apt-get install -y --no-install-recommends \
nvtop \
nvidia-smi \
&& rm -rf /var/lib/apt/lists/*
Extending the Recipe (baking more tools)
The Dockerfile is a recipe. Add whatever Debian packages or binaries you need.
Add Debian packages
# In Dockerfile, add to the RUN apt-get ... line
RUN apt-get update && apt-get install -y --no-install-recommends \
tree \
neofetch \
jq \
unzip \
zip \
&& rm -rf /var/lib/apt/lists/*
Add Ollama (for local LLM inference)
# Download latest ollama binary (Debian apt package is outdated)
RUN curl -sL https://ollama.ai/install.sh | sh
# Or pin to specific version
RUN curl -sL https://ollama.ai/install.sh | sh OLLAMA_VERSION=0.5.4
Add any binary
# Download pre-built binary and install to /usr/local/bin
RUN curl -sL "https://github.com/some/tool/releases/download/v1.0/tool-linux-amd64.tar.gz" | \
tar -xzf - -C /usr/local/bin && \
chmod 755 /usr/local/bin/tool
Rebuild after editing
cd /opt/hermes-opencode
docker compose build
docker compose up -d
Deploy Scripts
Fresh deploy (new VPS)
# From this machine:
bash /ext_storage/hermes-opencode/deploy.sh <VPS_IP>
# What it does:
# 1. rsync files to VPS
# 2. Create workspace/searxng directories
# 3. Generate SearXNG secret_key
# 4. Build Docker image
# 5. Start containers
# 6. Test persistence (docker restart + verify)
Volumes
| Volume | Mount Point | What lives here |
|---|---|---|
./hermes_data |
/opt/data |
Hermes config, memory, skills, profiles |
./opencode_data |
/home/hermes/.opencode |
OpenCode sessions, cache |
./workspace |
/home/hermes/workspace |
Working files (host-accessible) |
./searxng |
/etc/searxng |
SearXNG settings |
Environment Variables
| Variable | Default | Description |
|---|---|---|
HERMES_REAL_HOME |
/opt/data |
Redirects Hermes config to bind mount |
OPENCODE_API_KEY |
— | Optional API key for paid OpenCode models |
File Structure
hermes-opencode/
├── Dockerfile # Image recipe (base + packages + binaries)
├── docker-compose.yml # Containers + volumes + networks
├── deploy.sh # Fresh deploy to new VPS
├── README.md # This file
├── build.sh # Legacy build script
├── searxng/
│ └── settings.yml # SearXNG config (secret_key generated at deploy)
└── workspace/ # Working files (created at deploy)
Troubleshooting
Container exits immediately
docker logs hermes-opencode-hermes-opencode-1
OpenCode not found
docker exec hermes-opencode-hermes-opencode-1 which opencode
# → /usr/local/bin/opencode
Permission denied (workspace files)
sudo chown -R 10000:10000 /opt/hermes-opencode/workspace
Ollama not accessible from Hermes
# Check Ollama container IP
docker inspect ollama --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
# Verify both containers on same network
docker network inspect hermes-opencode_hermes-net
Network issues (SearXNG)
# Ensure both containers on same hermes-net
docker network inspect hermes-opencode_hermes-net
Credits
- Hermes Agent by Nous Research
- OpenCode — open-source AI coding agent
- SearXNG — privacy-respecting metasearch engine
评论区