| name | devrouter |
| description | Work with devrouter for local dev routing (HTTP + TCP/Postgres + dependency-only Docker services) |
| user-invocable | false |
devrouter
Local dev routing via a shared Traefik reverse proxy. Provides stable *.localhost hostnames for HTTP apps and TCP/Postgres multiplexing on shared ports (80, 443, 5432).
How it works
- Shared Traefik router owns host ports 80 (HTTP), 443 (HTTPS), 5432 (Postgres TCP).
- Per-repo config:
.devrouter.yml (single source of truth).
- Global runtime artifacts:
~/.config/devrouter (never edit manually).
- Hostnames must end with
.localhost (lowercase alphanumeric + hyphens only).
.devrouter.yml entry schema
version: 1
devrouter:
version: <semver>
project:
name: <string>
apps:
- name: <string>
kind: app | dependency
dependencies:
- app: <other-name>
envMap:
DATABASE_URL: <UPPER_DEP_NAME>_URL
host: <name>.localhost
protocol: http | tcp
runtime: host | docker | proxy
upstream: 127.0.0.1:3000
hostRun:
command: <string>
cwd: <string>
portTimeout: 120
strategy:
type: auto
denyPorts: [80, 443, 5432]
allowPortRange: '1024-65535'
docker:
service: <string>
internalPort: <number>
composeFiles: [<string>]
router: <string>
tcpProtocol: postgres
runtime: docker
docker:
service: <string>
composeFiles: [<string>]
Validation rules:
kind=app: host must end with .localhost
kind=app: runtime=host supports protocol=http only
kind=app: runtime=proxy supports protocol=http or protocol=tcp, requires upstream (host:port), and forbids hostRun/docker/dependencies (it only registers a route to an externally-managed upstream). protocol=tcp additionally requires tcpProtocol and TLS (devrouter tls install)
kind=app: protocol=tcp requires runtime=docker (devrouter-managed container) or runtime=proxy (externally-managed upstream), plus a supported tcpProtocol (postgres/redis/mariadb/mysql)
kind=dependency: must use runtime=docker and does not allow routed fields (host/protocol/tcpProtocol/hostRun/docker.internalPort/docker.router)
- Unknown keys rejected (strict schema)
Docker compose requirements
- Healthcheck required: every dependency service must define a
healthcheck. docker compose up --wait blocks until healthy; without one, wait returns immediately.
- No published ports: services must not publish host ports for devrouter-owned ports (80, 443, 5432). Avoid publishing ports at all -- devrouter handles routing via Traefik.
- Postgres credentials: use
POSTGRES_USER=prisma, POSTGRES_PASSWORD=prisma, POSTGRES_DB=prisma and create a shadow database. devrouter injects per-dep {PREFIX}_URL / {PREFIX}_SHADOW_URL with these credentials.
- Persistent volume warning: if postgres defaults changed on an existing volume, reconcile credentials/data or recreate volumes when safe (for example
docker compose down -v).
Example healthcheck:
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U prisma -d prisma']
interval: 5s
timeout: 3s
retries: 20
Env var injection
When a host app depends on a TCP Docker service, devrouter app run and devrouter app exec inject per-dep deterministic vars (where {PREFIX} = dep.name.toUpperCase().replace(/-/g, "_")):
| Variable | Value |
|---|
{PREFIX}_HOST | localhost |
{PREFIX}_PORT | random mapped port |
{PREFIX}_URL | protocol-specific URL (postgres, redis, mysql/mariadb) |
{PREFIX}_SHADOW_URL | postgres://prisma:prisma@localhost:<port>/shadow (postgres only) |
Host apps also receive PORT (random free port), HOSTNAME=0.0.0.0, HOST=0.0.0.0.
Config-level envMap on dependency references aliases per-dep vars to app-expected names (for example DATABASE_URL: DB_URL maps the per-dep DB_URL to DATABASE_URL).
Workspace isolation (parallel git worktrees / agents)
Run several worktrees of one repo in parallel without host/route collisions. A workspace token spans the DevPod id, devrouter routes, ${WORKSPACE} proxy upstreams, and devcontainer aliases.
- Identity: each managed linked worktree stores a local token in Git metadata plus a durable owner record in the repository's Git common directory. The record survives linked-worktree removal and binds the exact path to its DevPod ID. First use reuses an exact-path DevPod or derives a sanitized branch/path slug. Later flags or
DEVROUTER_WORKSPACE may repeat the identity but cannot rename it. Ambiguous identities fail closed. The primary checkout remains non-namespaced.
- When active: hosts auto-namespace (
web.localhost → web.<ws>.localhost), ${WORKSPACE} in upstream is substituted with the token, and the docker router key is suffixed per workspace. The runtime config is computed in memory only — the committed .devrouter.yml is never rewritten.
- TLS: namespaced hosts (
web.<ws>.localhost) are not covered by the *.localhost wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.
- devcontainer integration: managed scaffolds list the base compose file, then
${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}; custom repositories may keep another default overlay. Selecting .devcontainer/docker-compose.devrouter.yml for linked worktrees must pass WORKSPACE and DEVROUTER_WORKSPACE across the combined base/overlay config and bind-mount ${DEVROUTER_GIT_COMMON_DIR} to the same absolute app-container path. The app exposes ${WORKSPACE}-app; the proxy uses upstream: ${WORKSPACE}-app:<port>.
- Lifecycle:
workspace up creates and starts a worktree; workspace ensure . starts/reconciles an existing linked worktree; workspace ls reports owner/Git/DevPod/route state; workspace stop stops DevPod/routes but keeps checkout, record, and data; full workspace down deletes runtime/routes and removes only a clean, unlocked worktree, then its record; down --keep-worktree keeps checkout and record. Dirty or locked full down fails before side effects.
- Cleanup: owner status is
present, missing, locked, or conflict. workspace gc is a dry-run report; --yes deletes only exact ledger-owned missing/prunable resources and their records. GC never removes Git worktrees, branches, or prune state. Git has no worktree-removal hook.
- Boundary: workspace commands require Git. Normal config, app, status, and doctor flows remain usable from a
.devrouter.yml folder without .git.
Secret manager interop (Infisical/Doppler)
Upgrade handling (required)
- Keep
.devrouter.yml metadata devrouter.version aligned with the currently applied devrouter release.
- Verify versions with
devrouter -V (shows installed CLI version, local repo version, and next upgrade target).
- Use
devrouter upgrade to list available upgrade targets and devrouter upgrade <version> to print that target's Agent Adaptation Prompt from upgrade-prompts/<version>.md.
- Do not assume user-provided instructions include all required adaptation steps.
- After upgrading the CLI in a dependent repo, refresh discoverability artifacts with
devrouter repo agents (or devrouter init --write-agents --write-skill).
- Re-run validation after upgrade:
devrouter doctor --repo ., devrouter app ls --repo ., one representative devrouter app exec flow, and devrouter ls.
Commands
devrouter init [--write-agents] [--write-skill]: print AI onboarding prompt (non-mutating by default)
devrouter -V [--repo .]: show installed CLI version, local repo version, and next upgrade target
devrouter upgrade [version] [--repo .]: list upgrade targets or print target Agent Adaptation Prompt
devrouter setup --yes [--repo .] [--json]: first-run machine setup plus structured diagnostics
devrouter up / devrouter down: start/stop shared Traefik router
devrouter status: router/container/network/TLS health
devrouter doctor [--repo .]: deep diagnostics (global + repo)
devrouter ls: list active HTTP + TCP routes
devrouter open <name>: open HTTP route or print TCP connection hint (matches app name, then service/container/host identities)
devrouter logs [-f]: Traefik access logs
devrouter tls install: install mkcert certs, enable HTTPS + TCP/SNI
devrouter repo init: create .devrouter.yml
devrouter repo inspect [--json]: inspect package, scripts, compose services, env names, devcontainer, devrouter config, and agent guidance for onboarding
devrouter repo devcontainer write --dry-run --json: plan conservative Node/pnpm/Postgres devcontainer/devrouter scaffold files without writing
devrouter repo devcontainer write --yes: write managed Node/pnpm/Postgres devcontainer/devrouter scaffold files when no custom-file conflicts exist
devrouter repo devcontainer verify --json: emit read-only onboarding evidence for PRs
devrouter repo devcontainer verify --live --yes --json: register proxy routes and probe HTTP routes after the devcontainer is running
devrouter repo agents: write devrouter section in AGENTS.md + install this skill
devrouter app add: add/update app entry in .devrouter.yml
devrouter app ls: list app entries
devrouter app run <name> [--env <env>] [--workspace <slug>]: run app with dependency lifecycle (--env overrides SM defaultEnv; --workspace overrides the per-workspace token)
devrouter app exec <name> [--shell] [--env <env>] [--workspace <slug>] -- <cmd>: one-shot command with resolved dep env
devrouter app rm <name> [--keep-config]: remove app entry (--keep-config frees only the live route/hostname, leaves .devrouter.yml untouched)
devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]: create a worktree and start/prove it unless create-only mode is requested
devrouter workspace ensure [path] [--open]: start/reconcile and prove an existing linked worktree environment
devrouter workspace ls [--json]: list ownership, Git, DevPod, route, path, and branch evidence
devrouter workspace stop <workspace|branch>: stop DevPod and routes; preserve checkout, owner record, and data
devrouter workspace down <workspace|branch> [--keep-worktree]: delete runtime/routes and optionally remove the clean worktree and record
devrouter workspace gc [--json] [--yes]: report missing owners by default; apply exact eligible cleanup with --yes
Validation workflow
For devcontainer onboarding:
devrouter setup --repo . --yes --json
devrouter doctor --repo . --json
devrouter repo inspect --repo . --json
devrouter repo devcontainer write --repo . --dry-run --json
devrouter repo devcontainer write --repo . --yes
devrouter repo devcontainer verify --repo . --json
- In a linked worktree, start and prove the devcontainer with
devrouter workspace ensure . (use devpod up . for a primary checkout)
devrouter repo devcontainer verify --repo . --live --yes --json
For host/docker runtime apps only:
devrouter setup --repo . --yes
devrouter doctor --repo .
devrouter app ls --repo .
devrouter app run <host-app> --repo . --yes
devrouter ls
curl -I https://<host>.localhost
- For TCP/Postgres, use
devrouter open <name> for the connection hint.
Runtime behavior notes
devrouter app run auto-starts Docker dependencies and waits for health. Host app runs stop auto-started docker deps on exit; docker app runs leave target services running until explicit cleanup.
- Host-runtime dependencies are NOT auto-started (v1).
kind=dependency entries do not create routes and cannot be direct targets for devrouter app run, devrouter app exec, or devrouter open.
kind=dependency services start as declared in compose (no Traefik label wiring, no random port publishing, no injected env vars).
- Postgres on shared
:5432 requires TLS/SNI (devrouter tls install). Standard app clients should use the injected random port instead.
devrouter app exec follows the same dep lifecycle for one-shot commands and preserves argv semantics by default (shell: false).
devrouter app exec --shell is explicit and requires exactly one command string after --.
- Secret-manager overlap caveat: if Infisical/Doppler defines DB vars too, probe effective env (
printenv DB_URL DB_HOST DB_PORT) before migrate/seed.