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:

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."

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

CommandWhat it shows or does
git worktree listthe 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-rubythe agent containers of all three images running now, shells included
container/tools/ai/opencode-docker.sh ../site-agent-<task> shella 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