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:
- -s <id> продолжает сессию задачи со всей её историей.
- -m <model> выбирает платную модель только для этого прогона. Конфигурация шага 4 (конфигурация и заметки контейнера) оставляет бесплатную модель для всех остальных прогонов (другая модель на один прогон).
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."
- История сохраняется при смене модели. Проверено продолжением сессии на другой бесплатной модели: новая модель знала, какие файлы написал первый.
- Какие модели можно выбрать: запустите оболочку шага 6 (проверить, что видит контейнер) с опцией ключа, затем перечислите их через opencode models | grep '^opencode-go/' или opencode models | grep '^openrouter/'.
- Платная модель стоит денег. Проверьте цену до долгой задачи (проверяйте цены до планирования бюджета) и смотрите, сколько израсходовали прогоны, через opencode stats (7. ещё команды на каждый день).
- Ваш код уходит провайдеру модели. OpenCode Go и OpenRouter отправляют каждый запрос компании, которая запускает модель. Проверьте, хранит ли эта компания данные, прежде чем отправлять непубличный код (ключи, лимиты и приватность).
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 (рабочее дерево для агента).
Автор статьи: Клавдия Кондакова