Образ 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

Сборка и проверка

Собирать вручную не обязательно: запускающий скрипт (страница Запускающий скрипт) выполняет 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 ставит именно его. Что дают эти пакеты:

ЯзыкПакетыПроверено здесь
Erlangerlang (OTP 27) или erlang28собран и запущен: образ с Erlang
Rubyrubyсобран и запущен: образ с Ruby
Gogoсобран и запущен: пример: Go
Node.jsnodejs npm, или nodejs-current для более новой веткив списке
Javaopenjdk21-jdkв списке
Rustrust cargoв списке
Elixirelixirв списке
Cgcc musl-devв списке
Lualua5.4в списке
C#dotnet9-sdkв списке

2. Напишите Dockerfile. Скопируйте файл по умолчанию в Dockerfile.<language>.opencode, добавьте пакеты в конец строки apk add, перед && ln, и скажите в первом комментарии, что добавляет образ.

3. Дайте запускающему скрипту флаг для него, одна строка в скрипте (добавить флаг для другого языка).

4. Проверьте его в оболочке нового образа, как для образа с Erlang, и запишите его размер.

Пример: 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

Следующий прогон с его флагом соберёт его заново.

Автор статьи: Клавдия Кондакова