Skip to content

feat: add 9Router template - #1046

Open
DPS0340 wants to merge 4 commits into
Dokploy:canaryfrom
DPS0340:template/9router
Open

feat: add 9Router template#1046
DPS0340 wants to merge 4 commits into
Dokploy:canaryfrom
DPS0340:template/9router

Conversation

@DPS0340

@DPS0340 DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown

What

Adds a 9router template, implementing the request in #1040. The issue author noted they would not be sending a PR themselves.

9Router (23.5k stars, MIT) is a self-hosted AI model router: it rotates requests across 40+ providers with automatic fallback and exposes an OpenAI-compatible endpoint that coding agents (Claude Code, Codex, Cursor, Cline) can point at.

Files

  • blueprints/9router/docker-compose.yml
  • blueprints/9router/template.toml
  • blueprints/9router/meta.json
  • blueprints/9router/9router.png

How it was built

Derived from upstream's own docker-compose.yml, DOCKER.md and .env.example rather than written from scratch, then adapted to this repo's conventions:

  • Both images pinned. decolua/9router:0.5.40 (Docker Hub, linux/amd64 + linux/arm64, published 2026-07-20) and ghcr.io/chopratejas/headroom:0.6.7 (GHCR, linux/amd64 + linux/arm64, manifest verified HTTP 200 with a pull token). Upstream ships both as :latest.
  • Dropped ports: — upstream publishes 20128:20128 and 8787:8787; the domain mapping in template.toml handles this instead.
  • Dropped container_name: — upstream sets it on both services.
  • Dropped env_file: .env, which does not exist in a Dokploy deployment; the required variables are supplied through [config.env].
  • restart: alwaysunless-stopped to match the existing blueprints.
  • URLs use http:// per the URL convention in .github/copilot-instructions.md, and AUTH_COOKIE_SECURE stays "false" — which is also what upstream's .env.example ships.

Secrets come from upstream's .env.example, which documents JWT_SECRET and INITIAL_PASSWORD as required, plus API_KEY_SECRET and MACHINE_ID_SALT as recommended. All four are generated with ${password:N}, so no deployment ships a default credential.

Both BASE_URL and NEXT_PUBLIC_BASE_URL are wired to the assigned domain. The second one matters: it is the value the browser bundle uses, and without it the deployed UI falls back to upstream's http://localhost:20128 and calls localhost instead of its own domain.

The headroom sidecar is kept because upstream's compose wires HEADROOM_URL to it and the 9Router image does not bundle it.

Corrections made during review

Recording these so the history is readable rather than quietly amended:

  1. Retargeted from main to canary. canary is the default branch and 5 of the last 6 merged template PRs went there; main was 94 commits behind.
  2. headroom was originally :latest. My first description claimed the package "publishes only latest" — that was wrong. Checking the GHCR tag list properly found 100 tags including plain semver, so it is now pinned to 0.6.7.
  3. BASE_URL was originally https:// with AUTH_COOKIE_SECURE: "true". Both now follow the repo's HTTP-by-default convention and upstream's own defaults.
  4. NEXT_PUBLIC_BASE_URL was missing. Found while checking a review note about ROUTER_HOST; see the thread.

Testing

$ node build-scripts/generate-meta.js --check
✅ 501 templates validated

$ pnpm exec tsx validate-docker-compose.ts --file ../blueprints/9router/docker-compose.yml
✅ Docker Compose file structure is valid

Beyond CI I validated the config files by parsing them rather than by eye:

  • template.toml parses with a real TOML parser: 5 variables, 5 env entries, domain {serviceName: "9router", port: 20128, host: "${main_domain}"}serviceName matches the compose service name.
  • docker-compose.yml parses with a real YAML parser, and I asserted this repo's rules programmatically: no service declares ports, none declares container_name, no custom networks.
  • Cross-checked that every ${VAR} in the compose file is provided by template.toml: JWT_SECRET, INITIAL_PASSWORD, API_KEY_SECRET, MACHINE_ID_SALT, ROUTER_HOST — all five present.
  • Logo is upstream's official images/9router.png, resized 2940×2594 → 512×451 (758 KB → 108 KB).
  • Licence checked via the GitHub API: MIT, satisfying the open-source requirement.

What I could not do: I have no Dokploy instance, so this has not been deployed and clicked through. The PR preview deploy will be the first real run. Given CONTRIBUTING asks contributors to test before submitting, I want to be straight that my verification is config-level, image-manifest and licence checks — not a live deployment. Happy to convert to draft if you would rather hold it until someone can boot it.

@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. new-template labels Jul 26, 2026
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
templates ✅ Ready (View Log) Visit Preview 00b80f1

@DPS0340
DPS0340 force-pushed the template/9router branch from 7301e58 to 7dfd2fb Compare July 26, 2026 03:00
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Jul 26, 2026
@DPS0340
DPS0340 changed the base branch from main to canary July 26, 2026 03:00
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jul 26, 2026
@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Retargeted this from main to canary. I checked how recent template PRs land and 5 of the last 6 merged into canary (#1041, #1037, #1035, #1034, #1033); canary is also 94 commits ahead of main, so main was the wrong base. The branch is rebuilt on canary HEAD and the diff is still exactly the four blueprints/9router/ files.

@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Self-correction before review: the headroom sidecar was still on :latest, which contradicts the NEVER use \latest`rule in.github/copilot-instructions.md. In the PR body I'd said I couldn't pin it because the package "publishes only latest" — that was wrong, and checking properly rather than trusting my earlier note found **100 tags**, including plain semver ones. Pinned to 0.6.7(newest plainX.Y.Z), manifest verified HTTP 200 with a GHCR pull token. validate-docker-compose.ts` passes on the updated file.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Dokploy blueprint under blueprints/9router/ to deploy 9Router (AI model router / OpenAI-compatible proxy), including the Compose stack, Dokploy template configuration, and metadata/logo for indexing and UI display.

Changes:

  • Added docker-compose.yml defining the 9router service (pinned to decolua/9router:0.5.40) plus the headroom sidecar.
  • Added template.toml with generated secrets and domain/env wiring for Dokploy.
  • Added meta.json and a PNG logo so the template can be listed and discovered.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

File Description
blueprints/9router/docker-compose.yml Defines the 9Router + headroom services, images, env, and persistence volume.
blueprints/9router/template.toml Adds Dokploy variables, env injection, and domain routing to the 9router service port.
blueprints/9router/meta.json Registers template metadata (id/name/version/links/tags/logo).
blueprints/9router/9router.png Adds the template logo asset referenced by meta.json.

Comment thread blueprints/9router/docker-compose.yml Outdated
Comment on lines +16 to +18
BASE_URL: https://${ROUTER_HOST}
HEADROOM_URL: http://headroom:8787
AUTH_COOKIE_SECURE: "true"
@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

One more self-correction after reading .github/copilot-instructions.md properly (I found it via the Copilot review on #1048 — I had not read it when I opened this PR).

It says:

URL Variables: When environment variables require URLs (e.g., WEB_URL, NEXTAUTH_URL, PUBLIC_URL), always use HTTP by default … HTTPS should only be used if explicitly required by the application

I had BASE_URL: https://${ROUTER_HOST}. Changed to http:// in e01083f, and dropped AUTH_COOKIE_SECURE back to "false" to match — upstream's own .env.example ships both that way:

BASE_URL=http://localhost:20128
AUTH_COOKIE_SECURE=false

so this is now consistent with the repo convention and with what 9Router itself documents, rather than me second-guessing it.

Two other things I checked against that file while I was there, so they're not left implicit:

  • Open-source requirement — 9Router is MIT (confirmed via the GitHub license API), so it qualifies.
  • links.github required in meta.json — present (https://github.com/decolua/9router).

On the HTTP/HTTPS point I should flag that the repo is genuinely split: 134 occurrences use https://${...} and 103 use http://${...}, and a recently merged template (markup, #1024) uses https://. I've gone with http:// because that is what the written convention says and what upstream defaults to, but if the real preference is HTTPS for domain-backed services I'll switch it back — just say which you'd like and I'll follow that instead.

validate-docker-compose.ts passes on the updated file.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +12 to +18
JWT_SECRET: ${JWT_SECRET}
INITIAL_PASSWORD: ${INITIAL_PASSWORD}
API_KEY_SECRET: ${API_KEY_SECRET}
MACHINE_ID_SALT: ${MACHINE_ID_SALT}
BASE_URL: http://${ROUTER_HOST}
HEADROOM_URL: http://headroom:8787
AUTH_COOKIE_SECURE: "false"
Copilot AI review requested due to automatic review settings July 26, 2026 03:29
@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Two comments here — one I'd already fixed, one that sent me looking and turned up a genuine bug next to it.

1. BASE_URL https + AUTH_COOKIE_SECURE: true — already addressed in e01083f, just before this review landed. Both are now http:// and "false", matching the repo's URL convention and upstream's own .env.example. So we agree; the diff you reviewed was one commit stale.

2. ROUTER_HOST not passed into the container — I checked this rather than acting on it, and I don't think it needs to be. ROUTER_HOST is a name I invented for the Dokploy interpolation; 9Router itself never reads it. Its .env.example documents the complete set it consumes:

JWT_SECRET INITIAL_PASSWORD DATA_DIR PORT NODE_ENV API_KEY_SECRET
MACHINE_ID_SALT ENABLE_REQUEST_LOGS OBSERVABILITY_ENABLED AUTH_COOKIE_SECURE
REQUIRE_API_KEY BASE_URL CLOUD_URL NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_CLOUD_URL

No ROUTER_HOST. Passing it in would add an env var the app ignores. This also matches how other blueprints do it — e.g. activepieces interpolates AP_HOST into AP_FRONTEND_URL without also exporting AP_HOST to the container.

But chasing that list found something I had actually got wrong. Look at the last two entries: upstream reads NEXT_PUBLIC_BASE_URL as well, and I wasn't setting it. That is the NEXT_PUBLIC_* build/runtime variable the browser bundle uses, so without it the frontend would fall back to upstream's default of http://localhost:20128 — the deployed UI would try to call localhost instead of its own domain.

Fixed in 00b80f1:

BASE_URL: http://${ROUTER_HOST}
NEXT_PUBLIC_BASE_URL: http://${ROUTER_HOST}

So the second comment was right that something was missing from the container environment — just a different variable than the one named. Thanks for the prompt.

Re-verified after the change:

✅ Docker Compose file structure is valid
compose vars: JWT_SECRET,INITIAL_PASSWORD,API_KEY_SECRET,MACHINE_ID_SALT,ROUTER_HOST
all provided by template.toml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +25 to +29
# Optional token-reduction proxy that 9Router calls through HEADROOM_URL.
# Shipped as a sidecar because the 9Router image does not bundle it.
headroom:
image: ghcr.io/chopratejas/headroom:0.6.7
restart: unless-stopped
@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Right, and thank you for catching it — the description was stale and actively misleading, which is worse than it being merely out of date.

To confirm the factual part first: ghcr.io/chopratejas/headroom:0.6.7 does exist. Re-verified just now rather than relying on my earlier check:

manifest -> HTTP 200
platforms: linux/amd64, linux/arm64

The sequence was: I opened this PR claiming the package "publishes only `latest`" and therefore could not be pinned. That claim was wrong — when a Copilot review on #1048 pointed me at the NEVER use \latest`rule in.github/copilot-instructions.md, I re-checked the GHCR tag list properly and found **100 tags**, including plain semver. I pinned to 0.6.7inb9624e7` and posted a self-correction in the thread, but I did not go back and fix the PR body, so the two contradicted each other. That's on me.

The description is now rewritten. Both images are described as pinned with their verified platforms, and I've added a "Corrections made during review" section listing all four changes this PR went through (retarget to canary, the headroom pin, the https://http:// fix, and the missing NEXT_PUBLIC_BASE_URL) so a future maintainer reading it doesn't have to reconstruct the story from the comment thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-template size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants