OpenCode in Docker day to day: start, update, stop, continue and merge a task
Introduction
This article is part of a larger manual,
OpenCode in Docker on Ubuntu 24.04.
The manual shows how to run OpenCode with
the --auto option inside a
Docker container.
With --auto the agent does not ask you before
each command, so the container limits what it can reach: the agent works
only in one git worktree, a separate copy of the
project.
This article lists the commands you use every day, after you have
finished the setup.
You run all of them from the root folder of your main copy of the repo,
the main checkout.
In the commands, <task> is a short name
for the task, <id> is the id of a session
and <container> is the id of a container.
The commands below show where you get each of them.
1. Start a task
Commit first, then make a worktree and a branch for the task. The second command prints nothing:
git worktree add ../site-agent-<task> -b agent/<task>
git -C ../site-agent-<task> status --short --ignored
Then start the run. In this setup a task runs on the
free OpenCode model unless you
choose another one.
Read the article Select the
model to learn how to check which model a task uses, and how to run
it on an OpenCode Go or OpenRouter model instead.
Give the run a title. The session list shows it later, so you can tell the tasks apart:
container/tools/ai/opencode-docker.sh ../site-agent-<task> run --title <task> "<what to do>"
If the task is long, write it in a file and pass the text of the file in place of "<what to do>":
container/tools/ai/opencode-docker.sh ../site-agent-<task> run --title <task> "$(cat ~/tasks/<task>.md)"
The shell on the host reads the file before the launcher starts, so the
file can be anywhere on the host, and the container gets only its text.
Keep the file out of the worktree, so it does not end up in the commit
of the task.
The double quotes keep the whole text, line breaks included, as one
argument.
If the task has to change files outside
container/src, for example a script in
container/tools/<folder>, make that folder
writable for this run: put --rw and the folder
before the worktree. Everything else stays read-only.
For several folders repeat --rw (more in
When the task is in
container/tools):
container/tools/ai/opencode-docker.sh --rw container/tools/<folder> ../site-agent-<task> run --title <task> "<what to do>"
If you want to type the task yourself in the OpenCode window (the TUI) instead of passing it on the command line, leave out run and everything after it:
container/tools/ai/opencode-docker.sh ../site-agent-<task>
Which image: the default, --erlang or --ruby
The image is picked per run, by a flag before the worktree.
A task whose pages need Erlang starts with
--erlang, one for Ruby
with --ruby, any other without a flag
(the image per run:
--erlang and --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>"
The flag goes with the other options, in any order.
A task from a file, in the Erlang image, on an OpenCode Go model:
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)"
To move a task to another image, stop its run (3. stop a
task) and continue its session with the other flag
(4. continue after it ended).
The worktree stays the same, so the files the first run wrote are there;
what it kept in /tmp went with its container:
container/tools/ai/opencode-docker.sh --erlang ../site-agent-<task> run -s <id> "Erlang is installed now: run the examples."
Every image mounts the same sessions volume, so
-s finds the session whichever image the run
uses.
Checked by counting the sessions each image lists:
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
Which image each running container uses, and its worktree: the loop of
2. send an update while it runs.
docker ps --filter ancestor=opencode-agent alone
lists only the containers of the default image.
The other images are built from their own Dockerfiles, not from
opencode-agent, so each needs its own
--filter ancestor=; several of them add up.
2. Send an update while it runs
run holds its terminal until the task is done.
Send the update from a second terminal, into the same container. First
find the container.
The loop covers the containers of all three images; the second column is
the image, the third the worktree it works on (user name and test folder
replaced):
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
Its sessions, newest first, by title:
docker exec <container> opencode session list
ses_e5e8fead3ffeyVsc9Ck91Ae0zN exec-test 11/3/2026, 12:44:04 PM
Send the update to that session:
docker exec -i <container> opencode run --auto -s <id> "<update>"
It works because OpenCode in the container runs a background server,
opencode serve --service in the
ps list of the container.
A second opencode run in the same container
talks to that server.
The launcher is not part of this command, so
--auto is written out.
Tested on a run whose first step was sleep 90.
The update waited until that step was over.
Then the agent did it as part of the same task and wrote in its report
that the second file was "added per your update".
Both terminals printed the rest of the session, and both commands ended
with exit code 0 when the agent was done.
3. Stop a task
docker stop <container>
Tested on a run in the middle of a long command: it ended within two
seconds with exit code 130, and --rm removed the
container.
The session stays in the volume, and the files the agent has written so
far stay in the worktree.
To change course, stop the run and continue it with new instructions.
4. Continue after it ended
Every run starts a new session
(a new run does not
know the last one), so a follow-up has to name the old one.
With no container running, list the sessions straight from the volume:
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>"
Tested on the stopped run with "You were stopped. Skip the sleep and
finish the task.": the new container had the whole conversation and
wrote the file the first message asked for.
Start it on the worktree of the same task.
Another worktree does not hold the files the session remembers.
If the run ended because the free model reached its usage limit, see when the free model reaches its usage limit.
When the free model reaches its usage limit
The free OpenCode model can be
used only up to a limit.
When you reach it, the run stops at its next request to the model and
prints:
Error: Free usage limit reached. Connect a provider or subscribe to OpenCode Go to keep going, or switch models.
Nothing is lost. The session, with everything the agent has done so far,
stays in the sessions volume, and the files it wrote stay in the
worktree.
You can wait until the limit is reset, or continue the same task now on
a paid model.
To continue, start a new run on the same worktree with two options after
run:
- -s <id> continues the session of the task, with its whole history.
- -m <model> picks the paid model for this run only. The config of step 4 (the config and the container notes) keeps the free model for all other runs (another model for one run).
1. Find the id of the session. The newest session is the first
line.
If you started the task with --title, the title
is in the second column:
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. Continue on an OpenCode Go model. With an OpenCode Go subscription, pass your OpenCode key with --opencode-key and pick a Go model with -m, for example 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."
Read the section Keys for
one run of the launcher article to learn how to save the key in a
file and how to check that the container gets it.
If you logged in to OpenCode Go inside the container with
opencode auth login, the login is stored in the
sessions volume, and you can leave out
--opencode-key
(a stored login).
A long task of this setup ran on DeepSeek V4.1 Flash with such a stored
login.
The sessions volume shows it (shortened):
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. Or continue on a model through OpenRouter.
With an OpenRouter account, pass your
OpenRouter key with --openrouter-key and pick an
OpenRouter model:
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."
Read the article A paid model through OpenRouter to learn how to make the key and check it in the container.
Keep the rest of the command the same. Use the worktree of the
task, and the same image flag if the task needs one.
A task in the Erlang image, continued on 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."
- The history stays when the model changes. Tested by continuing a session on another free model: the new model knew which files the first one had written.
- Which models you can pick: start the shell of step 6 (check what the container sees) with the key option, then list them with opencode models | grep '^opencode-go/' or opencode models | grep '^openrouter/'.
- A paid model costs money. Check the price before a long task (verify prices before budgeting), and see what the runs used with opencode stats (7. more commands for every day).
- Your code goes to the provider of the model. OpenCode Go and OpenRouter send each request to the company that runs the model. Check whether that company keeps the data before you send code that is not public (keys, limits and privacy).
5. Read a report again
To read a report again without a new request to the model, export the session and keep the last answer. The filter is jq on the host:
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 ```
The report is the model's own account. In this run it had skipped
the sleep, as it was told, and still wrote that the sleep completed
(what the agent
checked).
The tasks the session was given, in order:
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. Review, commit and merge
The steps of review and merge, with the worktree of the task:
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
If the agent rebuilt the sitemap anyway:
git -C ../site-agent-<task> restore container/src/sitemap.xml
rm ../site-agent-<task>/container/src/sitemap.xml.bak-*
Bring the worktree up to date with main (main moved on while the agent worked), commit with explicit paths, and merge from the main checkout:
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>
If the task added or removed pages, rebuild the sitemap in the main checkout:
container/tools/generate-sitemap.sh
Then remove the worktree and the branch:
git worktree remove ../site-agent-<task>
git branch -d agent/<task>
7. More commands for every day
| Command | What it shows or does |
|---|---|
| git worktree list | the open tasks: one line per worktree not removed yet |
| git log --oneline main..agent/<task> | the commits of a task that are not in main yet |
| git diff --stat main...agent/<task> | the files a task changes, compared with main |
| git branch --merged main | grep agent/ | agent branches already in main; + marks one that still has a worktree to remove |
| docker ps --filter ancestor=opencode-agent --filter ancestor=opencode-agent-erlang --filter ancestor=opencode-agent-ruby | the agent containers of all three images running now, shells included |
| container/tools/ai/opencode-docker.sh ../site-agent-<task> shell | a shell in the container of a task, to run a check the way the agent runs it, as in step 6 (check what the container sees) |
| docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent session delete <id> | deletes one session; prints Session <id> deleted |
What today's runs used, per model. With a paid model the cost column is what to watch:
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
The launcher refuses a worktree you ran the tests in
Tests leave ignored folders behind, and the launcher then refuses to continue the task in that worktree (user name replaced):
ERROR: /home/me/github/site-agent-ruby-functions holds ignored files; use a fresh worktree: !! container/.pytest_cache/ !! container/.venv/
git clean -X removes ignored files only, never your edits or new files. First list what it would remove:
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
Tested on a throwaway worktree: after it the launcher started the container again. The tests belong in the main checkout, after the merge, as step 2 (a worktree for the agent) explains.
Article author: Klavdiya Kondakova