RouterOS /app container for T3 Code —
the agentic coding tool that runs as a server with a web UI. This repo builds
multi-arch (linux/amd64 + linux/arm64) OCI images to
ghcr.io/tikoci/app-t3code, ships the /app YAML to install them, and
proves the whole path on real CHR VMs (x86_64 + ARM64) with Bun tests.
Target device: MikroTik RDS2216 (ARM64, ROSE edition — container-ready out of the box). Anything here that says CHR also applies to RDS, except the CHR-only workarounds are marked.
Prerequisites on the router (one time):
- RouterOS 7.24.6 or newer (the minimum supported version).
- The
containerextra package installed. - Device-mode with
container=yes(RDS shipsrosemode with it on; other hardware needs one physical confirm). - A formatted disk for apps (see Storage).
Then install the app for production (HTTPS via /ip/cloud or your own domain):
/app/add yaml=([/tool/fetch url="https://tikoci.dev/app-t3code/app/t3code.tikapp.yaml" check-certificate=yes output=user as-value]->"data") use-https=yes
/app/set [find where name="t3code"] disabled=no
Note
When using CHR, such as with tikoci/quickchr,
change use-https=yes to use-https=no. Free-license CHR has no /ip/cloud
for certificate provisioning. CI and the test commands below use this setting.
Once the container is running, follow Pairing with the web UI
to get its browser URL and a one-time pairing token. Storage persists on the
app disk, so logins, the T3 database, and ~/projects survive reboots.
See in /app YAML Editor to inspect, validate, or customize the single-app definition in our sister project, tikoci/restraml. The share link embeds the YAML as a snapshot; the downloads below track the current published files. Both YAML files include schema comments for editor validation.
GitHub Pages mirrors the repository paths for Markdown and the two app YAML formats, serving the original files without rendering or rewriting them:
The install command above fetches the app YAML into memory and passes its
contents to /app/add yaml= for a one-time installation.
Tip
Alternatively, add our app store to the router's catalog. This appends to
app-store-urls, preserving existing stores. Before you install the app,
the catalog can pick up changes to our template so installation uses the
latest YAML we publish.
/app/settings/set app-store-urls=([get app-store-urls],"https://tikoci.dev/app-t3code/app/t3code.tikappstore.yaml")
The store requires a YAML array, so use .tikappstore.yaml for
app-store-urls, and .tikapp.yaml for a single app installation.
See MikroTik's custom app store documentation.
Use a separate disk for apps; the RouterOS root volume cannot be used.
Format the app disk as ext4 or btrfs, then set its mount point in
/app/settings. /app refuses to enable anything until this is configured:
/disk/print # find the extra disk, e.g. *1
/disk/format *1 file-system=ext4
/app/settings/set disk=<mount-point>
The disk can be any supported storage type with enough space for the image
and persistent data. On CHR, attach a second virtual disk. The app then lives
under <disk>/apps/t3code/.
The image with bundled agent tools is approximately 1GB compressed and 4GB
extracted; allow at least an 8GB app disk for installation, plus space for
projects, browser downloads, and any quickchr lab disks. Builds need extra
space for intermediate layers and loading the finished image.
The existing named volume t3code-data:/data holds the entire home.
Both the image and app YAML explicitly set HOME=/data and
T3CODE_HOME=/data/.t3. Keep the app name, volume name, and mount destination
when upgrading. The executable in /opt/t3code comes from the new image;
your home comes from the existing disk volume.
| Container path | Contents |
|---|---|
/data/.t3/userdata |
T3 databases, settings, environment identity, pairing/auth state, secrets, attachments, browser profiles |
/data/.t3/projects, /data/.t3/scratch, /data/.t3/worktrees |
T3-managed projects, projectless threads, and worktrees (including uncommitted files) |
/data/projects |
Repositories you create or clone here |
/data/.config, /data/.local/share, provider homes |
Provider configuration, logins, and session history |
/data/.local, /data/.bun |
User-installed tools and Bun global packages |
Projects outside /data need their own persistent mount. A project record
in the database does not preserve a checkout stored in the container's
writable layer. Provider settings that override a home or XDG path must also
point inside persistent storage.
Keep one home volume: splitting only userdata from the rest of .t3 can
preserve project records while losing their worktrees or scratch files.
Do not clear .t3 during upgrades. Its runtime, tools, and caches
directories contain downloadable runtimes and caches, but worktrees and
userdata contain user work. Use T3's
storage cleanup settings
to manage disk use. The paths above are grounded in upstream's
server path definitions
and workspace folders.
Back up the complete /data volume with the app stopped before upgrading
(databases and their sidecars must be copied together). Keep external project
volumes in the same backup. Update the installed app in place:
/app/update [find where name="t3code"]
If the registry image is unchanged, this may retain the existing root filesystem. Existing installations receive the new image's home defaults and tools through this command; they do not need the YAML reapplied.
Warning
On CHR 7.25.1, assigning /app/set ... yaml=... recreated the app
storage and deleted the existing named-volume contents. This reproduced
with a comment-only YAML change on a running app. Treat any YAML change as
a migration: stop the app and back up the complete /data volume, external
project volumes, and every other app-owned volume somewhere outside
<disk>/apps/t3code/. Record each volume's source and container destination.
Apply the YAML, then keep the app stopped while restoring the home and other
app-owned volumes into their newly selected mount sources. Verify external
project volumes are mounted at their recorded destinations; restore their
backups if their data was removed or their storage moved. Verify ownership
and mount mappings for every volume before enabling the app.
Splitting the home into more app-owned volumes does not avoid this reset.
RouterOS
/app cleanup
deletes app data as well as the image; it is a factory reset, not an upgrade.
Deleting/recreating the app or changing the app disk also requires a backup
and an explicit restore to the intended home volume.
If an upgrade appears to have lost projects, stop the app and check its
/container mount mapping and the old app disk before creating new projects.
Check whether the original .t3/userdata/statev2.sqlite and checkout folders
still exist. A new mount, changed home, or a checkout outside /data can
look like data loss even when the original files are still on disk.
The container serves plain HTTP on 3773 (T3CODE_HOST=0.0.0.0,
T3CODE_PORT=3773). Put HTTPS in front for anything beyond localhost —
the hosted web/mobile T3 apps require it.
The browser needs a one-time token to connect to this container. Once the
app is running, run this command in the RouterOS terminal to print both
the app's browser URL and a fresh token from t3-pairing-link inside the
app-t3code container:
:put "** Using T3Code **\r\n\r\nIn your browser go to: $[/app/get t3code ui-url]\r\n\r\nThen, use the Token below in your browser to connect it with this container:\r\n$[/container/shell app-t3code cmd="t3-pairing-link" as-value]"
For example (illustrative token):
** Using T3Code **
In your browser go to: http://10.0.2.15:3773/
Then, use the Token below in your browser to connect it with this container:
Issued client pairing token 00000000-0000-4000-8000-000000000000.
Token: EXAMPLEONLY
Expires at: 2026-11-03T14:07:30.038Z
Open the printed URL and enter the value on the Token: line in the browser's
pairing prompt. Tokens expire after about five minutes; run the command again
if you need a fresh one.
Note
ui-url reflects the app's use-https setting. With use-https=yes, it
reports the HTTPS URL (including the MikroTik hostname when using /ip/cloud).
With use-https=no, it reports an HTTP URL like the CHR example above.
If PUBLIC_URL is set in the container, t3-pairing-link can print a full
pairing URL instead of a raw token.
| Env | Default | Notes |
|---|---|---|
T3CODE_HOST / T3CODE_PORT |
0.0.0.0 / 3773 |
server bind |
T3CODE_TELEMETRY_ENABLED |
false in our YAML |
set true/unset to opt back in |
PUBLIC_URL |
unset | base URL baked into pairing links |
ANTHROPIC_API_KEY, OPENAI_API_KEY, … |
inherited | agents see container env |
T3CODE_* |
passthrough | e.g. T3CODE_SERVER_BROWSER_SANDBOX=0 |
Codex, opencode, and Meta Muse Code ship in the image;
Claude Code installs itself to the persistent volume on first boot
(its license forbids redistributing it, so it cannot be baked in).
Muse uses the native binary downloaded and verified by Meta's official
installer during the image build. Run muse to authenticate, or supply
META_API_KEY; its default Linux configuration and history live under
/data. Baked tools update when you replace the image.
The image includes Bun 1.4.4, bunx, and
global installations of:
@tikoci/quickchr@next— README: create disposable RouterOS CHR labs. QEMU for x86 and ARM64,qemu-img, and ARM64 UEFI firmware are included.@tikoci/centrs@next— README: inspect and drive RouterOS through validated commands and structured results.
They are available to agents and login shells without a first-run install:
bun --version
quickchr --help
centrs explain '/ip/address print' --json
muse --versionQEMU can use software emulation inside a RouterOS container. KVM acceleration
requires an accessible /dev/kvm and host support; installing quickchr does
not grant it. Size lab storage and memory for both T3 and any guest routers.
Image-owned Bun packages live in /opt/bun; user
bun install --global packages live
in /data/.bun. PATH searches /data/.local/bin, /data/.bun/bin, then
/opt/bun/bin before system tools. User-installed overrides persist and take
precedence after an image upgrade. Build arguments BUN_VER, QUICKCHR_VER,
and CENTRS_VER can select specific releases instead of the defaults.
Good upstream navigators (pingdotgg/t3code docs):
- install / requirements (
t3 serve, providers) — install.md - remote access (
t3 up,--lan, T3 Connect relay, pairing) — remote-access.md - background service (memory/OOM guidance,
t3 service) — background-service.md - telemetry opt-out — telemetry.md
- project settings / storage cleanup — project-settings.md
- FAQ (disk usage is worktree cleanup) — faq.md
The image only documents one requirement: an installed, authenticated provider. There are no published CPU/RAM minimums; size from your workload (agents + browser), not from a datasheet.
Images track upstream stable T3 Code releases only — no nightlies
(CI still accepts any T3CODE_VER input for one-off builds):
ghcr.io/tikoci/app-t3code:0.1.6— exact releaseghcr.io/tikoci/app-t3code:0.1,:0,:latest— stability aliases
The /app YAML references the :0.1-style stability tag, and
test/app-yaml.test.ts fails the build if it drifts from
package.json's major.minor. Bumping T3 versions = bump T3CODE_VER
(default in Dockerfile + Makefile), package.json, and the YAML
tag together.
bun install
bun run check # biome + tsc --noEmit
bun run test # offline /app YAML contract (always runs)
APP_T3CODE_INTEGRATION=1 bun test test/integration/
# Build the image, then prove the tools and container-replacement persistence:
docker build -t app-t3code:test .
APP_T3CODE_DOCKER_IMAGE=app-t3code:test bun test test/container-replacement.test.tsThe integration test boots a real CHR per arch with
@tikoci/quickchr@next, drives it with @tikoci/centrs@next, and
asserts the web UI answers with the expected serverVersion:
APP_T3CODE_ROS=stable APP_T3CODE_ARCH=arm64 APP_T3CODE_INTEGRATION=1 \
bun test test/integration/The Docker replacement test removes the original container and starts a new one with only the same home volume. It checks tool discovery in ordinary and login shells, a real T3 project record and environment identity, settings (including the restarted server's effective preference through read-only MCP), uncommitted worktree/project files, and a user-installed Bun CLI. CI runs this against the built x86 image before publishing, and builds both image architectures. It does not authenticate providers or run paid model requests.
Manual CHR 7.25.1 checks also confirmed the /data mount, node UID 1000,
T3 0.1.6's web endpoint, and project/environment identity through an in-place
/app update. The unchanged registry digest retained the root filesystem;
the Docker test proves full replacement. Separately, a fixture written
through /data remained on the app disk after disable and container removal,
but disappeared after assigning YAML, including a comment-only change with
the original container still present. This is a reproduced destructive path;
the original agent's precise upgrade commands are unknown.
Both helpers float on next on purpose: if a helper breaks, report it
to centrs /
quickchr instead of
working around it silently. CI (ci.yml) lints, tests, and builds
both arches to GHCR; integration.yml runs the CHR matrix weekly,
post-push on app/ + test/integration/, and on demand.
The pages.yml workflow publishes only tracked *.md, *.tikapp.yaml, and
*.tikappstore.yaml files, preserving their paths and contents. Automatic
deployments run only when those files change on main; manual dispatch is
also available. Pages must use GitHub Actions as its publishing source
in the repository's Settings → Pages. No site generator is used.
Verified install flow on CHR 7.25 (x86, free license):
- first-boot
deviceMode: { enable: ["container"] }→rosemode (the guest-side/system/device-mode/updatenever completes); installPackage("container")(/app404s without the package);/disk/format <id> file-system=ext4(7.25 hasext4, noext3)/app/settings/set disk=<mount-point>;
api … app -X PUT -f yaml=… -f use-https=no --yes(colon-style3773:3773:web:tcpports parse asfirewall-redirects);/app/set … disabled=no(PUT leaves the appdisabled:true);- poll
/app+/container(.runningis the string"true") +GET /.well-known/t3/environment→serverVersion.
routeros-app-yaml, routeros-container, routeros-centrs,
routeros-quickchr-cli, and tikoci-crossref skills govern this repo;
AGENTS.md has the working notes.
MIT — see LICENSE.