9 Commits

Author SHA1 Message Date
e6e5eadcf8 Allow opencode to run Docker images via a dind sidecar
opencode now drives a dedicated Docker-in-Docker container instead of the
host daemon, so it can build/run images and bring up compose stacks from
within its own container.

Notes:
- dind runs in PRIVILEGED mode, this required for a nested docker daemon.
- opencode itselfs remains unprivileged
- opencode talks to dind over TCP (DOCKER_HOST=tcp://dind:2375)
  Consequence: services started in dind are reachable at dind:<port>, not
  localhost.
- The workspace needs to be bind-mounted at the same absolute path in both containers
  so compose bind-mounts resolve inside the dind daemon.
- /var/lib/docker is persisted on a mounted volume to keep images warm across
restarts.
- uses the rpio registry mirror for quicker pulls
2026-07-06 12:51:32 +02:00
48caec8669 improve readme readability and add some tutorials 2026-07-06 12:48:03 +02:00
Aad Versteden
1f357fe2e3 Remove version keyword
Because Docker doesn't understand that version has a value for documentation, we
otherwise get an error.
2026-07-02 17:07:28 +02:00
Aad Versteden
4425dcfd41 Curate models for getting started easily 2026-07-02 17:05:39 +02:00
felix
539d2eb4d6 remove trailing whitespace 2026-07-02 16:21:12 +02:00
5675c5e75a Merge pull request 'adds scaleway glm as an alternative to glm from deepinfra' (#1) from scaleway-glm into master
Reviewed-on: #1
2026-07-02 14:17:56 +00:00
felix
1d45a7caea remove Dockerfile 2026-07-02 16:17:15 +02:00
felix
8a4e21fc2e Update README.md 2026-07-02 16:17:02 +02:00
felix
113db0b0dd Build version 2026-07-02 16:13:14 +02:00
4 changed files with 161 additions and 143 deletions

162
README.md
View File

@@ -1,51 +1,153 @@
# app-opencode-experiment # app-opencode
[opencode](https://opencode.ai/) in a Docker container, wired to [weave](https://weave.redpencil.io) as the model provider. Default model `weave/glm-5.2`. LSP baked in for TypeScript/JS and Python. [opencode](https://opencode.ai/) packaged in a Docker container and wired to
[weave](https://weave.redpencil.io) as the model provider. It ships with an
LSP for TypeScript/JavaScript and Python baked in, so opencode gets real
type-aware navigation out of the box. The default model is `weave/glm-5.2`.
## Setup The docs below are split into three parts: a **Getting started** walkthrough to
run it the first time, **How-to guides** for specific tasks, and a **Reference**
for the moving parts.
Create a `docker-compose.override.yml` (gitignored) with your weave key and repo mount: > `drc` in the examples is shorthand for `docker compose`
> (`alias drc='docker compose'`). Substitute `docker compose` if you don't have
> the alias.
```yaml ## Getting started
services:
opencode:
environment:
WEAVE_API_KEY: "..."
volumes:
- /path/to/your/repo:/workspace/repo
```
Then: A first run from a clean checkout.
``` 1. **Get a weave API key.** You need an account on
drc up -d [weave.redpencil.io](https://weave.redpencil.io/ui/) and/or an API key (ask us!).
drc exec opencode opencode
```
## TUI 2. **Create your local override.** `docker-compose.override.yml` is gitignored
and holds your key plus the repo you want opencode to work on. Create it with
your key and mount:
In the TUI, type `/models` to select a model from the registered weave models. ```yaml
services:
opencode:
environment:
WEAVE_API_KEY: "your-key-here"
volumes:
- /path/to/your/repo:/workspace/repo
```
## Layout 3. **Start the container.**
- `config/opencode/` - image: Dockerfile + `opencode.json` (mounted live, see below) ```
- `data/` - persisted opencode state (gitignored) drc up -d
- `docker-compose.yml` - opencode service ```
## Updating the config 4. **Open the TUI.**
`opencode.json` is mounted into the container, so edits take effect with a restart — no rebuild needed: ```
drc exec opencode opencode
```
You're now in opencode, talking to `weave/glm-5.2`. Type `/models` to switch
models, or just start prompting.
## How-to guides
### Switch the model in a session
In the TUI, type `/models` and pick from the registered weave models. This only
changes the current session; to change the default for every session, edit
`opencode.json` (see below).
### Change the default model
Edit `model` (and optionally `small_model`) in
[`config/opencode/opencode.json`](config/opencode/opencode.json), then apply the
change:
``` ```
drc restart opencode drc restart opencode
``` ```
Rebuild only when changing the `Dockerfile` or installed packages: `opencode.json` is mounted into the container, so a restart is all that's
needed.
``` ### Register a new weave model
drc build opencode
Add the model under `provider.weave.models` in `opencode.json`:
```json
"provider": {
"weave": {
"models": {
"my-new-model": { "tools": true }
}
}
}
``` ```
## Caveats Then `drc restart opencode`. It will show up in the `/models` picker.
See the [model list on weave](https://weave.redpencil.io/ui/models-and-endpoints/) for an overview of available models. Need another model, please let us know :)
- Copy from the TUI is weird over Docker: To copy text out of the opencode TUI running in a container, I have to do `Shift`+click to select the text, then `Ctrl`+`Shift`+`C` to copy. Plain `COPY` doesn't work. ### Copy text out of the TUI
Copying from the opencode TUI over Docker is fiddly: `Shift`+click to select the
text, then `Ctrl`+`Shift`+`C` to copy. A plain copy doesn't work.
### Connect opencode to a (backend) service
To let opencode call a service over HTTP, e.g. a stack you've brought up in dev
mode and want the agent to follow up against, have that stack join opencode's
network instead of publishing ports or going via the host.
opencode's stack creates a default network named `app-opencode_default`. Attach the
dev-mode service to it as an external network:
```yaml
# the dev-mode app stack
services:
app:
networks:
- default
- app-opencode_default
networks:
app-opencode_default:
external: true
```
opencode can then reach it at `http://app:<port>`, where `app` is the **service
name** from that stack's compose and `<port>` is the container's *internal* port —
no `ports:` publishing needed.
Note: Bring the opencode stack up first, so `app-opencode_default` exists before adding to your app.
### Executing docker commands from opencode
The stack runs a dedicated dind (docker in docker) container, and opencode can drive it as a client (`DOCKER_HOST=tcp://dind:2375`). So from inside opencode you can build images, run containers, and `docker compose up` a project's stack. The agent can spin up and test the services it's working on.
A few things that differ from a normal host, because the daemon lives in dind:
- Containers and published ports you start run on the **dind** daemon. Reach a running service at `dind:<port>`, **not** `localhost:<port>`. opencode does not
share dind's network namespace, so `localhost` won't reach the stack.
- The repo needs to be mounted at the same absolute path (`/workspace/repo`) in both the opencode and dind containers, so compose bind-mounts resolve correctly. Work on the checkout there; edits elsewhere won't be visible to the containers dind runs.
- The dind image store is cached in `./data/dind-cache`, so pulled images stay warm across restarts.
- To access the services from the host machine, you will need to connect to the dind container or publish the necessary ports from the dind container to your host.
> Note: Opencode itself doesn't run in a privileged container so it can't do privileged actions directly, but it can use the docker daemon in dind to do the same things. Because dind is privileged and in the host user namespace, a container started inside dind can mount the host's raw disk devices and reach the **entire** host filesystem, not only what you shared. This needs opencode to actively do it, but it's a well-known technique, not a theoretical edge case.
## Reference
### `opencode.json` keys
- **`model` / `small_model`** — default model and the model used for lighter
tasks. Both default to `weave/glm-5.2`.
- **`provider.weave`** — the weave provider, using the
`@ai-sdk/openai-compatible` adapter against `https://weave.redpencil.io/v1`
with the key from `WEAVE_API_KEY`.
- **`provider.weave.models`** — the models offered in `/models`.
- **`lsp`** — language servers baked into the image:
`typescript-language-server` for TS/JS and `pyright` for Python.
- **`permission`** — set to `ask` for read, edit, bash, and external-directory
actions, so opencode prompts before acting.
### Caveats
- Copy from the TUI over Docker needs the `Shift`+click / `Ctrl`+`Shift`+`C`
dance described above.

View File

@@ -1,19 +0,0 @@
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
git curl ca-certificates python3 \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g \
opencode-ai@1.17.9 \
typescript \
typescript-language-server \
pyright
ENV NODE_PATH=/usr/local/lib/node_modules
COPY opencode.json /root/.config/opencode/opencode.json
WORKDIR /workspace
CMD ["sleep", "infinity"]

View File

@@ -38,109 +38,22 @@
"apiKey": "{env:WEAVE_API_KEY}" "apiKey": "{env:WEAVE_API_KEY}"
}, },
"models": { "models": {
"claude-sonnet": { "kimi-K2.7-code": {
"tools": true
},
"claude-opus": {
"tools": true
},
"claude-haiku": {
"tools": true
},
"Mistral-Small-3.2-24B-Instruct-2506": {
"tools": true
},
"phi-4": {
"tools": true
},
"gemini-3.5-flash": {
"tools": true
},
"gemini-3.1-pro": {
"tools": true
},
"qwen3.6-35B-A3B": {
"tools": true
},
"qwen3-coder-480B-A35B-Instruct": {
"tools": true "tools": true
}, },
"glm-5.2": { "glm-5.2": {
"tools": true "tools": true
}, },
"scaleway-glm-5.2": { "devstral": {
"tools": true,
"options": {
"max_tokens": 16384
}
},
"kimi-K2.7-code": {
"tools": true "tools": true
}, },
"MiniMax-M2.7": { "local-qwen3.6:27b": {
"tools": true
},
"DeepSeek-V4-Flash": {
"tools": true
},
"DeepSeek-V4-Pro": {
"tools": true
},
"nemotron-3-ultra": {
"tools": true
},
"mimo-v2.5-pro": {
"tools": true
},
"qwen3-max-thinking": {
"tools": true
},
"gpt-oss-120b": {
"tools": true
},
"mistral-large": {
"tools": true "tools": true
}, },
"mistral-medium": { "mistral-medium": {
"tools": true "tools": true
}, },
"mistral-small": { "claude-sonnet": {
"tools": true
},
"magistral-medium": {
"tools": true
},
"magistral-small": {
"tools": true
},
"codestral": {
"tools": true
},
"devstral": {
"tools": true
},
"mistral-embed": {
"tools": false
},
"mistral-ocr": {
"tools": false
},
"ministral-14b": {
"tools": true
},
"ministral-8b": {
"tools": true
},
"ministral-3b": {
"tools": true
},
"local-gemma3:27b-it-qat": {
"tools": true
},
"local-gpt-oss:20b": {
"tools": true
},
"local-qwen3.6:27b": {
"tools": true "tools": true
} }
} }

View File

@@ -1,8 +1,30 @@
version: '3.7'
services: services:
opencode: opencode:
build: ./config/opencode/ image: redpencil/opencode:feature-with-docker
volumes: volumes:
- ./data/opencode/share:/root/.local/share/opencode - ./data/opencode/share:/root/.local/share/opencode
- ./config/opencode/opencode.json:/root/.config/opencode/opencode.json - ./config/opencode/opencode.json:/root/.config/opencode/opencode.json
# - ./config/opencode:/root/.config/opencode
depends_on:
dind:
condition: service_healthy
environment:
DOCKER_HOST: "tcp://dind:2375"
dind:
image: docker:29.6-dind
privileged: true
# userns mode only required if you use user namespaces
userns_mode: "host"
environment:
DOCKER_TLS_CERTDIR: ""
volumes:
- ./data/dind-cache:/var/lib/docker
command:
- "--registry-mirror=https://docker-registry-mirror.redpencil.io"
healthcheck:
test: ["CMD", "docker", "info"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s