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 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 and opencode 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).
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/The 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/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.