OpenCode в Docker день за днём: запустить, обновить, остановить, продолжить и слить задачу

Введение

Эта статья — часть большого руководства OpenCode в Docker на Ubuntu 24.04.

Руководство показывает, как запускать OpenCode с параметром --auto внутри контейнера Docker.

С --auto агент не спрашивает вас перед каждой командой, поэтому контейнер ограничивает то, до чего он может добраться: агент работает только в одном рабочем дереве git, отдельной копии проекта.

Эта статья перечисляет команды, которые вы используете каждый день, после того как закончили настройку.

Все они выполняются из корневой папки вашей главной копии репозитория — главной копии.

В командах <task> — короткое имя задачи, <id> — id сессии, а <container> — id контейнера.

Команды ниже показывают, откуда берётся каждое из них.

1. Запустить задачу

Сначала коммит, затем рабочее дерево и ветка для задачи. Вторая команда ничего не печатает:

git worktree add ../site-agent-<task> -b agent/<task>

git -C ../site-agent-<task> status --short --ignored

Затем запустите прогон. В этой настройке задача идёт на бесплатной модели OpenCode, если вы не выберете другую.

Статья Выбор модели рассказывает, как проверить, какая модель используется в задаче, и как вместо неё запустить задачу на модели OpenCode Go или OpenRouter.

Дайте прогону заголовок. Список сессий покажет его позже, и вы сможете отличать задачи друг от друга:

container/tools/ai/opencode-docker.sh ../site-agent-<task> run --title <task> "<what to do>"

Если задача длинная, напишите её в файл и передайте текст файла вместо "<what to do>":

container/tools/ai/opencode-docker.sh ../site-agent-<task> run --title <task> "$(cat ~/tasks/<task>.md)"

Оболочка на хосте читает файл до старта запускающего скрипта, поэтому файл может лежать где угодно на хосте, а контейнер получает только его текст.

Держите файл вне рабочего дерева, чтобы он не попал в коммит задачи.

Двойные кавычки сохраняют весь текст, включая переводы строк, как один аргумент.

Если задача должна изменить файлы вне container/src, например скрипт в container/tools/<folder>, сделайте эту папку доступной на запись для этого прогона: поставьте --rw и папку перед рабочим деревом. Всё остальное останется доступным только на чтение.

Для нескольких папок повторите --rw (подробнее в когда задача в container/tools):

container/tools/ai/opencode-docker.sh --rw container/tools/<folder> ../site-agent-<task> run --title <task> "<what to do>"

Если вы хотите набрать задачу сами в окне OpenCode (TUI), а не передавать её в командной строке, опустите run и всё, что после него:

container/tools/ai/opencode-docker.sh ../site-agent-<task>

Какой образ: по умолчанию, --erlang или --ruby

Образ выбирается на каждый прогон флагом перед рабочим деревом.

Задача, чьим страницам нужен Erlang, начинается с --erlang, задача для Ruby — с --ruby, любая другая — без флага (образ на каждый прогон: --erlang и --ruby):

container/tools/ai/opencode-docker.sh --erlang ../site-agent-<task> run --title <task> "<what to do>"

container/tools/ai/opencode-docker.sh --ruby ../site-agent-<task> run --title <task> "<what to do>"

Флаг идёт вместе с другими опциями в любом порядке.

Задача из файла, в образе с Erlang, на модели OpenCode Go:

container/tools/ai/opencode-docker.sh --erlang --opencode-key ~/.config/opencode-agent/opencode.key ../site-agent-<task> run --title <task> -m opencode-go/deepseek-v4.1-flash "$(cat ~/tasks/<task>.md)"

Чтобы перенести задачу в другой образ, остановите её прогон (3. остановить задачу) и продолжите её сессию с другим флагом (4. продолжить после завершения).

Рабочее дерево остаётся тем же, поэтому файлы первого прогона на месте; то, что он держал в /tmp, ушло вместе с его контейнером:

container/tools/ai/opencode-docker.sh --erlang ../site-agent-<task> run -s <id> "Erlang is installed now: run the examples."

Каждый образ монтирует один и тот же том сессий, поэтому -s находит сессию в любом образе.

Проверено подсчётом сессий, которые показывает каждый образ:

for i in opencode-agent opencode-agent-erlang opencode-agent-ruby; do printf "%-22s %s sessions\n" $i "$(docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode $i session list 2>/dev/null | grep -c '^ses_')"; done

opencode-agent 16 sessions opencode-agent-erlang 16 sessions opencode-agent-ruby 16 sessions

Какой образ использует каждый работающий контейнер и на каком рабочем дереве: цикл из 2. отправить обновление во время работы.

Один docker ps --filter ancestor=opencode-agent перечисляет только контейнеры образа по умолчанию.

Остальные образы собраны из своих Dockerfile, а не из opencode-agent, поэтому каждому нужен свой --filter ancestor=; несколько таких фильтров складываются.

2. Отправить обновление во время работы

run занимает свой терминал, пока задача не закончена.

Отправьте обновление из второго терминала в тот же контейнер. Сначала найдите контейнер.

Цикл охватывает контейнеры всех трёх образов; второй столбец — образ, третий — рабочее дерево, на котором он работает (имя пользователя и тестовая папка заменены):

for c in $(docker ps -q --filter ancestor=opencode-agent --filter ancestor=opencode-agent-erlang --filter ancestor=opencode-agent-ruby); do echo "$c $(docker inspect -f '{{.Config.Image}} {{range .Mounts}}{{if eq .Destination "/work/container/src"}}{{.Source}}{{end}}{{end}}' $c)"; done

a3c48770c15f opencode-agent-erlang /home/me/github/site-agent-test/container/src dcef64716f7e opencode-agent /home/me/github/site-agent-test/container/src 14aeb4f23dab opencode-agent-ruby /home/me/github/site-agent-test/container/src

Его сессии, новые сверху, по заголовкам:

docker exec <container> opencode session list

ses_e5e8fead3ffeyVsc9Ck91Ae0zN exec-test 11/3/2026, 12:44:04 PM

Отправьте обновление в эту сессию:

docker exec -i <container> opencode run --auto -s <id> "<update>"

Это работает, потому что OpenCode в контейнере запускает фоновый сервер — opencode serve --service в списке ps контейнера.

Второй opencode run в том же контейнере разговаривает с этим сервером.

Запускающий скрипт в эту команду не входит, поэтому --auto написан явно.

Проверено на прогоне, первым шагом которого был sleep 90.

Обновление ждало, пока этот шаг закончится.

Затем агент выполнил его в рамках той же задачи и написал в отчёте, что второй файл «добавлен по вашему обновлению».

Оба терминала напечатали остаток сессии, и обе команды завершились с кодом 0, когда агент закончил.

3. Остановить задачу

docker stop <container>

Проверено на прогоне в середине длинной команды: он завершился за две секунды с кодом 130, а --rm удалил контейнер.

Сессия остаётся в томе, а файлы, которые агент уже написал, остаются в рабочем дереве.

Чтобы сменить курс, остановите прогон и продолжите его с новыми инструкциями.

4. Продолжить после завершения

Каждый run начинает новую сессию (новый прогон не знает о прошлом), поэтому продолжение должно назвать старую.

Когда контейнер не работает, перечислите сессии прямо из тома:

docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent session list

ses_e5e8da30bffeEnviT4HOFaoHVm stop-test 11/3/2026, 12:46:32 PM ses_e5e8fead3ffeyVsc9Ck91Ae0zN exec-test 11/3/2026, 12:45:42 PM

container/tools/ai/opencode-docker.sh ../site-agent-<task> run -s <id> "<what to change or finish>"

Проверено на остановленном прогоне с «You were stopped. Skip the sleep and finish the task.»: новый контейнер получил весь разговор и написал файл, о котором просило первое сообщение.

Запускайте его на рабочем дереве той же задачи.

Другое рабочее дерево не содержит файлов, которые помнит сессия.

Если прогон закончился потому, что бесплатная модель упёрлась в лимит, смотрите когда бесплатная модель упирается в лимит.

Когда бесплатная модель упирается в лимит

Бесплатную модель OpenCode можно использовать только до лимита.

Когда вы его достигаете, прогон останавливается на следующем запросе к модели и печатает:

Error: Free usage limit reached. Connect a provider or subscribe to OpenCode Go to keep going, or switch models.

Ничего не теряется. Сессия со всем, что агент сделал до сих пор, остаётся в томе сессий, а написанные им файлы остаются в рабочем дереве.

Можно подождать сброса лимита или продолжить ту же задачу прямо сейчас на платной модели.

Чтобы продолжить, начните новый прогон на том же рабочем дереве с двумя опциями после run:

1. Найдите id сессии. Самая новая сессия — первая строка.

Если вы начинали задачу с --title, заголовок стоит во втором столбце:

docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent session list

ses_e5e8da30bffeEnviT4HOFaoHVm stop-test 11/3/2026, 12:46:32 PM ses_e5e8fead3ffeyVsc9Ck91Ae0zN exec-test 11/3/2026, 12:45:42 PM

2. Продолжите на модели OpenCode Go. С подпиской OpenCode Go передайте свой ключ OpenCode через --opencode-key и выберите модель Go через -m, например DeepSeek V4.1 Flash:

container/tools/ai/opencode-docker.sh --opencode-key ~/.config/opencode-agent/opencode.key ../site-agent-<task> run -s <id> -m opencode-go/deepseek-v4.1-flash "Continue the task."

Раздел Ключи на один прогон статьи о запускающем скрипте рассказывает, как сохранить ключ в файл и как проверить, что контейнер его получил.

Если вы вошли в OpenCode Go внутри контейнера через opencode auth login, вход сохранён в томе сессий, и --opencode-key можно не указывать (сохранённый вход).

Долгая задача этой настройки шла на DeepSeek V4.1 Flash с таким сохранённым входом.

Том сессий это показывает (сокращённо):

docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent stats --days 3 --models

last 3 days · all projects [...] MODELS model tokens steps cost opencode-go/deepseek-v4.1-flash#d… 61.2m 287 $0.34 opencode/deepseek-v4.1-flash-free 26.6m 318 $0.00

3. Или продолжите на модели через OpenRouter.

С учётной записью OpenRouter передайте свой ключ OpenRouter через --openrouter-key и выберите модель OpenRouter:

container/tools/ai/opencode-docker.sh --openrouter-key ~/.config/opencode-agent/openrouter.key ../site-agent-<task> run -s <id> -m openrouter/deepseek/deepseek-v4.1-flash "Continue the task."

Статья Платная модель через OpenRouter рассказывает, как сделать ключ и проверить его в контейнере.

Остальную часть команды не меняйте. Используйте рабочее дерево задачи и тот же флаг образа, если задаче он нужен.

Задача в образе с Erlang, продолженная на OpenCode Go:

container/tools/ai/opencode-docker.sh --erlang --opencode-key ~/.config/opencode-agent/opencode.key ../site-agent-<task> run -s <id> -m opencode-go/deepseek-v4.1-flash "Continue the task."

5. Прочитать отчёт снова

Чтобы прочитать отчёт снова без нового запроса к модели, экспортируйте сессию и оставьте последний ответ. Фильтр — это jq на хосте:

sudo apt install jq

docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent session export <id> | jq -r '[.messages[] | select(.type=="assistant") | .content[] | select(.type=="text") | .text] | last'

Done. The sleep completed on the retry, and `/work/c.txt` now contains: ``` three ```

Отчёт — это собственный рассказ модели. В этом прогоне она пропустила sleep, как ей и сказали, и всё равно написала, что sleep завершился (что проверил агент).

Задачи, которые получила сессия, по порядку:

docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent session export <id> | jq -r '.messages[] | select(.type=="user") | .text'

"Run the shell command 'sleep 120', then write the word three into /work/c.txt." "You were stopped. Skip the sleep and finish the task."

6. Ревью, коммит и слияние

Шаги ревью и слияния с рабочим деревом задачи:

git -C ../site-agent-<task> status --short

git -C ../site-agent-<task> diff --stat

git -C ../site-agent-<task> diff

find ../site-agent-<task>/container -type l

Если агент всё-таки пересобрал карту сайта:

git -C ../site-agent-<task> restore container/src/sitemap.xml

rm ../site-agent-<task>/container/src/sitemap.xml.bak-*

Приведите рабочее дерево в актуальное состояние с main (main ушёл вперёд, пока агент работал), сделайте коммит с явными путями и слейте из главной копии:

git -C ../site-agent-<task> merge --ff-only main

git -C ../site-agent-<task> add <paths>

git -C ../site-agent-<task> commit -m "<message>"

git merge agent/<task>

Если задача добавила или удалила страницы, пересоберите карту сайта в главной копии:

container/tools/generate-sitemap.sh

Затем удалите рабочее дерево и ветку:

git worktree remove ../site-agent-<task>

git branch -d agent/<task>

7. Ещё команды на каждый день

КомандаЧто показывает или делает
git worktree listоткрытые задачи: одна строка на рабочее дерево, которое ещё не удалено
git log --oneline main..agent/<task>коммиты задачи, которых ещё нет в main
git diff --stat main...agent/<task>файлы, которые меняет задача, в сравнении с main
git branch --merged main | grep agent/ветки агентов, уже влитые в main; + отмечает ту, у которой ещё есть рабочее дерево для удаления
docker ps --filter ancestor=opencode-agent --filter ancestor=opencode-agent-erlang --filter ancestor=opencode-agent-rubyконтейнеры агентов всех трёх образов, работающие сейчас, включая оболочки
container/tools/ai/opencode-docker.sh ../site-agent-<task> shellоболочка в контейнере задачи, чтобы запустить проверку так, как её запускает агент, как в шаге 6 (проверить, что видит контейнер)
docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent session delete <id>удаляет одну сессию; печатает Session <id> deleted

Что израсходовали сегодняшние прогоны, по моделям. С платной моделью столбец стоимости — то, за чем нужно следить:

docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent stats --days 0 --cost --models

today · all projects COST & TOKENS cost $0.00 input 3.6k output 522 reasoning 382 cache read 46.7k cache write 0 cached input 92.8% MODELS model tokens steps cost opencode/deepseek-v4.1-flash-free 45.9k 9 $0.00 opencode/glm-5.3-flash-free#defau… 5.4k 1 $0.00

Скрипт отклоняет рабочее дерево, где вы гоняли тесты

Тесты оставляют после себя игнорируемые папки, и запускающий скрипт отказывается продолжать задачу в таком рабочем дереве (имя пользователя заменено):

ERROR: /home/me/github/site-agent-ruby-functions holds ignored files; use a fresh worktree: !! container/.pytest_cache/ !! container/.venv/

git clean -X удаляет только игнорируемые файлы и никогда — ваши правки или новые файлы. Сначала перечислите, что он удалит:

git -C ../site-agent-<task> clean -ndX -- container

Would remove container/.pytest_cache/ Would remove container/.venv/ Would remove container/tests/__pycache__/ Would remove container/tools/feed/__pycache__/

git -C ../site-agent-<task> clean -fdX -- container

Проверено на одноразовом рабочем дереве: после этого запускающий скрипт снова запустил контейнер. Тестам место в главной копии, после слияния, как объясняет шаг 2 (рабочее дерево для агента).

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