Образ OpenCode в Docker: PHP по умолчанию, Erlang, Ruby, Go и другие языки
Введение
Эта статья — часть большого руководства
OpenCode в Docker на Ubuntu 24.04.
Руководство показывает, как запускать OpenCode
с параметром --auto внутри контейнера
Docker.
С --auto агент не спрашивает вас перед каждой
командой, поэтому контейнер ограничивает то, до чего он может добраться:
агент работает только в одном рабочем дереве
git, отдельной копии проекта.
Эта статья полностью раскрывает шаг 3
(образ) руководства.
Она объясняет, почему официального образа OpenCode мало, и показывает
образ по умолчанию, который добавляет PHP и другие
программы.
Затем идут ещё два образа: один с
Erlang и один с
Ruby.
Запускающий скрипт использует их, когда вы начинаете прогон с
--erlang или --ruby.
В конце вы узнаете, как сделать образ для другого языка.
Все Dockerfile лежат в папке
container/containerfiles.
Зачем свой образ
Образ, скачанный в шаге 1 (Docker и образ OpenCode), запускает OpenCode, но не проверки этого проекта. Он работает от root и не содержит программ, которые вызывают навыки:docker run --rm --entrypoint sh ghcr.io/anomalyco/opencode:2.1.4 -c 'id; php -v; bash --version; python3 --version; perl -v'
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) sh: php: not found sh: bash: not found sh: python3: not found sh: perl: not found
А то, что он пишет в смонтированную папку, принадлежит root на хосте. Из пустой папки:
docker run --rm -v "$PWD:/work" --entrypoint sh ghcr.io/anomalyco/opencode:2.1.4 -c 'touch /work/page.php'
ls -l page.php
-rw-r--r-- 1 root root 0 Oct 26 23:49 page.php
echo '<?php' >> page.php
bash: page.php: Permission denied
Образ по умолчанию
Поэтому вы собираете свой образ поверх него.FROM берёт скачанный образ, а строки после него добавляют программы и пользователя с вашим UID:
vi container/containerfiles/Dockerfile.opencode
# OpenCode for --auto runs on an agent worktree. The official image, plus # PHP for php -l and page renders, bash, Python, Perl and the GNU tools for # the scripts in container/tools, and a user with your UID, so the files the # agent writes stay yours. FROM ghcr.io/anomalyco/opencode:2.1.4 RUN apk add --no-cache bash python3 perl grep sed findutils coreutils \ php83 php83-ctype php83-dom php83-iconv php83-mbstring php83-session \ php83-simplexml php83-tokenizer php83-xml \ && ln -s /usr/bin/php83 /usr/local/bin/php ARG HOST_UID=1000 ARG HOST_GID=1000 RUN addgroup -g ${HOST_GID} agent \ && adduser -D -u ${HOST_UID} -G agent agent \ && mkdir -p /work && chown agent:agent /work USER agent # Created by the user, so a new named volume mounted here is writable. RUN mkdir -p /home/agent/.local/share/opencode WORKDIR /work
- Официальный образ — это Alpine Linux с OpenCode, grep и ripgrep, поэтому пакеты ставятся через apk.
- Что вызывают инструменты репозитория. PHP 8.3 с расширениями, которые нужны страницам: без php83-ctype каждая страница останавливалась с Call to undefined function ctype_digit(). Bash, Python и Perl для скриптов. Версии GNU для grep, sed, find и coreutils, потому что версиям из BusyBox не хватает опций, которые используют скрипты.
- Закреплённая версия. Тег — то, что печатает opencode --version на хосте. Обновляйте оба вместе.
- Пользователь с вашим UID. Файлы, которые пишет агент, принадлежат вам, а сам агент не может устанавливать пакеты.
- Git намеренно отсутствует. Коммиты делаются на хосте, после ревью и слияния на хосте.
Сборка и проверка
Собирать вручную не обязательно: запускающий скрипт (страница
Запускающий скрипт) выполняет
docker build при каждом старте, поэтому изменение
Dockerfile вступает в силу при
следующем прогоне.
Первая сборка заняла 33 секунды, следующие берутся из кеша. Чтобы
собрать один раз самому и сравнить:
docker build -t opencode-agent --build-arg HOST_UID=$(id -u) --build-arg HOST_GID=$(id -g) -f container/containerfiles/Dockerfile.opencode container/containerfiles
docker run --rm --entrypoint sh opencode-agent -c 'id; php -v | head -1; bash --version | head -1; python3 --version; perl -v | sed -n 2p'
uid=1000(agent) gid=1000(agent) groups=1000(agent) PHP 8.3.36 (cli) (built: Oct 21 2026 16:38:20) (NTS) GNU bash, version 5.3.9(1)-release (x86_64-alpine-linux-musl) Python 3.14.8 This is perl 5, version 42, subversion 2 (v5.42.2) built for x86_64-linux-thread-multi
Образ занимает 354 MB, на 94 MB больше официального.
Образ с Erlang
Задача, которая проверяет страницы Erlang,
должна запускать их примеры, а в образе по умолчанию нет
erl.
Образ с Erlang — это файл по умолчанию с ещё одним пакетом в конце
строки apk, erlang,
и первым комментарием, который об этом говорит:
vi container/containerfiles/Dockerfile.erlang.opencode
# The image of Dockerfile.opencode plus Erlang/OTP (erl, erlc, escript), so # the agent can run the examples of the Erlang pages. OpenCode for --auto # runs on an agent worktree. The official image, plus PHP for php -l and page # renders, bash, Python, Perl and the GNU tools for the scripts in # container/tools, and a user with your UID, so the files the agent writes # stay yours. FROM ghcr.io/anomalyco/opencode:2.1.4 RUN apk add --no-cache bash python3 perl grep sed findutils coreutils \ php83 php83-ctype php83-dom php83-iconv php83-mbstring php83-session \ php83-simplexml php83-tokenizer php83-xml \ erlang \ && ln -s /usr/bin/php83 /usr/local/bin/php ARG HOST_UID=1000 ARG HOST_GID=1000 RUN addgroup -g ${HOST_GID} agent \ && adduser -D -u ${HOST_UID} -G agent agent \ && mkdir -p /work && chown agent:agent /work USER agent # Created by the user, so a new named volume mounted here is writable. RUN mkdir -p /home/agent/.local/share/opencode WORKDIR /work
По сравнению с файлом по умолчанию отличаются только комментарий и эта строка:
diff container/containerfiles/Dockerfile.opencode container/containerfiles/Dockerfile.erlang.opencode
1,4c1,6 < # OpenCode for --auto runs on an agent worktree. The official image, plus < # PHP for php -l and page renders, bash, Python, Perl and the GNU tools for < # the scripts in container/tools, and a user with your UID, so the files the < # agent writes stay yours. --- > # The image of Dockerfile.opencode plus Erlang/OTP (erl, erlc, escript), so > # the agent can run the examples of the Erlang pages. OpenCode for --auto > # runs on an agent worktree. The official image, plus PHP for php -l and page > # renders, bash, Python, Perl and the GNU tools for the scripts in > # container/tools, and a user with your UID, so the files the agent writes > # stay yours. 8a11 > erlang \
Запускающий скрипт собирает его как
opencode-agent-erlang, когда прогон начинается с
--erlang
(образ на каждый прогон:
--erlang и --ruby).
Оболочка в нём, на рабочем дереве из
шага 2 (рабочее дерево для
агента):
container/tools/ai/opencode-docker.sh --erlang ../site-agent shell
В приглашении /work $, версия:
erl -noshell -eval 'io:format("~s~n", [erlang:system_info(otp_release)]), halt().'
27
Модуль, скомпилированный через erlc и запущенный через erl, в /tmp, где место черновым файлам:
cd /tmp
vi hello.erl
-module(hello). -export([hi/0]). hi() -> io:format("hi from a module~n").
erlc hello.erl
erl -noshell -s hello hi -s init stop
hi from a module
И скрипт для escript:
vi s.escript
#!/usr/bin/env escript main(_) -> io:format("hi from escript~n").
escript s.escript
hi from escript
Образ занимает 435 MB, на 81 MB больше образа по умолчанию.
Пакет erlang в Alpine 3.24 — это Erlang/OTP
27; erlang28 дал бы OTP 28
(другой язык).
Образ с Ruby
То же для страниц Ruby: файл по умолчанию плюс пакет ruby, который приносит irb, gem и erb:
vi container/containerfiles/Dockerfile.ruby.opencode
# The image of Dockerfile.opencode plus Ruby (ruby, irb, gem, erb), so the # agent can run the examples of the Ruby pages. OpenCode for --auto runs on # an agent worktree. The official image, plus PHP for php -l and page # renders, bash, Python, Perl and the GNU tools for the scripts in # container/tools, and a user with your UID, so the files the agent writes # stay yours. FROM ghcr.io/anomalyco/opencode:2.1.4 RUN apk add --no-cache bash python3 perl grep sed findutils coreutils \ php83 php83-ctype php83-dom php83-iconv php83-mbstring php83-session \ php83-simplexml php83-tokenizer php83-xml \ ruby \ && ln -s /usr/bin/php83 /usr/local/bin/php ARG HOST_UID=1000 ARG HOST_GID=1000 RUN addgroup -g ${HOST_GID} agent \ && adduser -D -u ${HOST_UID} -G agent agent \ && mkdir -p /work && chown agent:agent /work USER agent # Created by the user, so a new named volume mounted here is writable. RUN mkdir -p /home/agent/.local/share/opencode WORKDIR /work
Оболочка с --ruby:
container/tools/ai/opencode-docker.sh --ruby ../site-agent shell
В приглашении /work $:
ruby -v
ruby 3.4.12 (2026-10-23 revision 366f5fdc00) +PRISM [x86_64-linux-musl]
ruby -e 'puts [3, 1, 2].sort.inspect'
[1, 2, 3]
gem -v
3.6.9
Образ занимает 373 MB. Первый старт с --ruby, который его и собрал, занял 56 секунд.
Другой язык
Образ для другого языка делается в четыре шага.
1. Найдите пакет. Пакеты берутся из
Alpine Linux, системы официального
образа.
Агент не может ничего установить, поэтому смотрите имена от root в
одноразовом контейнере образа по умолчанию.
Контейнер завершается вместе с командой, и от него ничего не
остаётся:
docker run --rm --user root --entrypoint sh opencode-agent -c 'apk update -q >/dev/null 2>&1; apk search -e go ruby erlang nodejs npm openjdk21-jdk rust cargo elixir gcc musl-dev lua5.4 dotnet9-sdk'
cargo-1.96.1-r0 dotnet9-sdk-9.0.123-r0 elixir-1.19.7-r0 erlang27-27.3.4.19-r0 erlang28-28.5.0.8-r0 gcc-15.2.0-r5 go-1.26.10-r0 lua5.4-5.4.8-r0 musl-dev-1.2.6-r3 nodejs-24.22.1-r1 nodejs-current-26.12.1-r0 npm-11.12.1-r0 openjdk21-jdk-21.0.13.0.3_p1-r0 ruby-3.4.12-r0 rust-1.96.1-r0
-e ищет точные имена. Каждая строка — это пакет
и его версия в Alpine 3.24.
Самого erlang в списке нет: это имя, которое
предоставляет erlang27, и apk add
erlang ставит именно его. Что дают эти пакеты:
| Язык | Пакеты | Проверено здесь |
|---|---|---|
| Erlang | erlang (OTP 27) или erlang28 | собран и запущен: образ с Erlang |
| Ruby | ruby | собран и запущен: образ с Ruby |
| Go | go | собран и запущен: пример: Go |
| Node.js | nodejs npm, или nodejs-current для более новой ветки | в списке |
| Java | openjdk21-jdk | в списке |
| Rust | rust cargo | в списке |
| Elixir | elixir | в списке |
| C | gcc musl-dev | в списке |
| Lua | lua5.4 | в списке |
| C# | dotnet9-sdk | в списке |
2. Напишите Dockerfile. Скопируйте файл по умолчанию в Dockerfile.<language>.opencode, добавьте пакеты в конец строки apk add, перед && ln, и скажите в первом комментарии, что добавляет образ.
3. Дайте запускающему скрипту флаг для него, одна строка в скрипте (добавить флаг для другого языка).
4. Проверьте его в оболочке нового образа, как для образа с Erlang, и запишите его размер.
- Один образ на прогон. Задача, которой нужны два языка, получает один Dockerfile с обоими пакетами.
- Версии приходят с базой. Alpine 3.24 — система образа OpenCode 2.1.4; новый тег OpenCode может принести новый Alpine и новые версии всех пакетов.
- Всё остальное не меняется. Пользователь с вашим UID, без root и без git: то, до чего агент может добраться, от языка не зависит.
Пример: Go
Go, шаг за шагом. Файл:
vi container/containerfiles/Dockerfile.go.opencode
# The image of Dockerfile.opencode plus Go (go, gofmt), so the agent can run # the examples of the Go pages. FROM ghcr.io/anomalyco/opencode:2.1.4 RUN apk add --no-cache bash python3 perl grep sed findutils coreutils \ php83 php83-ctype php83-dom php83-iconv php83-mbstring php83-session \ php83-simplexml php83-tokenizer php83-xml \ go \ && ln -s /usr/bin/php83 /usr/local/bin/php ARG HOST_UID=1000 ARG HOST_GID=1000 RUN addgroup -g ${HOST_GID} agent \ && adduser -D -u ${HOST_UID} -G agent agent \ && mkdir -p /work && chown agent:agent /work USER agent RUN mkdir -p /home/agent/.local/share/opencode WORKDIR /work
С флагом --go, добавленным в запускающий скрипт (добавить флаг для другого языка), оболочка в нём:
container/tools/ai/opencode-docker.sh --go ../site-agent shell
В приглашении /work $:
go version
go version go1.26.10 linux/amd64
cd /tmp
vi hello.go
package main import "fmt" func main() { fmt.Println("hi from go") }
go run hello.go
hi from go
Проверено в одноразовом рабочем дереве, где файл и флаг были добавлены
только там.
Первый старт собрал образ за 106 секунд, и он занимает 737 MB: один
набор инструментов Go — 177 MB на диске.
Образы на диске
Каждый образ, который собрал запускающий скрипт, остаётся, пока вы его не удалите:
docker image ls 'opencode-agent*'
IMAGE ID DISK USAGE CONTENT SIZE EXTRA opencode-agent-erlang:latest d12ea898f281 435MB 0B U opencode-agent-ruby:latest f11ce3d8efce 373MB 0B opencode-agent:latest 9430341af54f 354MB 0B
U отмечает образ, который прямо сейчас
использует контейнер.
Занятое место не складывается просто так: образы делят слои
официального образа. Чтобы удалить образ, который больше не нужен:
docker image rm opencode-agent-ruby
Следующий прогон с его флагом соберёт его заново.
Автор статьи: Клавдия Кондакова