Repository navigation
Docs: Running Cortex on a remote machine #1607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # Running Cortex on a remote machine | ||
|
|
||
| Cortex on a laptop is one process bound to loopback. On a remote machine — a VM, a | ||
| home server, a cluster pod — it is the same process with the same binding, and the | ||
| machine is reached over an encrypted tunnel that carries loopback across the network. | ||
| Your SSH key or cluster identity is the authentication; Cortex never learns it is | ||
| remote, and nothing about its security model changes. | ||
|
|
||
| ## The rule | ||
|
|
||
| **Every Cortex port stays on loopback, and the tunnel is the only way in.** Never | ||
| expose a port directly, and never put one behind an ingress: | ||
|
|
||
| - The session API (`:47601`) is unauthenticated by design and serves raw prompts, | ||
| completions and tool results. | ||
| - The forward proxy (`:47600`) carries your agent's decrypted traffic and its | ||
| credentials. | ||
|
|
||
| One assumption carries over from the laptop: loopback is *yours*. On a shared | ||
| host every local account reaches both ports without the tunnel, so this model | ||
| assumes a machine you are the only user of — or one whose every account you | ||
| trust, the same caveat as a shared OpenShift project below. | ||
|
|
||
| A tunnel — `ssh -L`, `kubectl port-forward`, SSH over Tailscale — gives you both | ||
| encryption and "only me" without Cortex growing an auth subsystem. It also keeps the | ||
| session API's browser guards passing: a tunnelled request arrives with a loopback | ||
| `Host`, so erasing sessions (`DELETE /v1/sessions`) keeps working from afar. | ||
|
|
||
| ## Ports | ||
|
|
||
| A local-style install binds, on the remote machine's loopback: | ||
|
|
||
| | Port | What it is | | ||
| |---|---| | ||
| | `47600` | Forward proxy — where agents point `HTTPS_PROXY` | | ||
| | `47601` | Session API — what `agentop` reads | | ||
| | `47602` | Stats — `/reload/status`, live config, pprof | | ||
| | `47604` | Health | | ||
|
|
||
| ## Deploying | ||
|
|
||
| Cortex needs a writable `HOME` (it writes `~/.cortex`) and nothing else — no | ||
| Keycloak, no SPIRE, no cluster. Run the release binary with `--local`, which writes | ||
| and watches `~/.cortex/config.yaml`. | ||
|
|
||
| As a systemd user service (verified on Ubuntu 24.04, aarch64): | ||
|
|
||
| ```ini | ||
| # ~/.config/systemd/user/cortex.service | ||
| [Unit] | ||
| Description=Cortex | ||
|
|
||
| [Service] | ||
| ExecStart=%h/.local/bin/cortex --local | ||
| Restart=on-failure | ||
|
|
||
| [Install] | ||
| WantedBy=default.target | ||
| ``` | ||
|
|
||
| ```sh | ||
| systemctl --user enable --now cortex.service | ||
| loginctl enable-linger "$USER" # keep it running after you log out | ||
| ``` | ||
|
|
||
| Or skip the hand-written unit: `agentop service install` (with agentop installed on | ||
| the server) writes and supervises an equivalent one, with quoted absolute paths and | ||
| restart backoff. It writes to the same `~/.config/systemd/user/cortex.service`, so | ||
| running it replaces a hand-written unit. In a container, run the binary with `HOME` | ||
| pointed at a volume. | ||
|
|
||
| ## Connecting agentop | ||
|
|
||
| Forward the session API and point agentop at your end of the tunnel: | ||
|
|
||
| ```sh | ||
| ssh -N -L 47601:localhost:47601 you@server | ||
| agentop observe --endpoint http://localhost:47601 | ||
| ``` | ||
|
|
||
| Everything works through the tunnel: the session list, the live event stream, the | ||
| timeline, cost — and erasing sessions, because the request arrives with a loopback | ||
| `Host`. If you also run a local Cortex on the same machine, forward to a free port | ||
| (`-L 48601:localhost:47601`) and name it in `--endpoint`. | ||
|
|
||
| ## Where the agents run | ||
|
|
||
| **On the remote box, beside Cortex** — nothing more to do. The agents use | ||
| `http://127.0.0.1:47600` as their proxy, exactly as on a laptop; see the per-agent | ||
| guides in [`agents/`](agents/). | ||
|
|
||
| **On your machine, through the remote Cortex** — forward the proxy port too and | ||
| point the agent at it: | ||
|
|
||
| ```sh | ||
| ssh -N -L 47600:localhost:47600 you@server | ||
| ``` | ||
|
|
||
| Then set `HTTPS_PROXY=http://localhost:47600` for the agent, and trust the remote | ||
| bridge CA — copy it over and export it: | ||
|
|
||
| ```sh | ||
| scp you@server:.cortex/ca/ca.crt ~/.cortex/remote-ca.pem | ||
| export NODE_EXTRA_CA_CERTS=~/.cortex/remote-ca.pem | ||
| ``` | ||
|
|
||
| `NODE_EXTRA_CA_CERTS` *adds* to Node's own roots, so the bare remote CA is the right | ||
| file for it. Variables that *replace* the trust store (`SSL_CERT_FILE`, | ||
| `GIT_SSL_CAINFO`, `CURL_CA_BUNDLE`, `REQUESTS_CA_BUNDLE`) must not get it — they | ||
| would trust one private CA and nothing else — and the server's `bundle.crt` is no | ||
| substitute either, since it carries the *server's* public roots. Give those a file | ||
| holding your machine's roots plus the remote CA, e.g. your local `bundle.crt` with | ||
| `remote-ca.pem` appended. [`agents/any-agent.md`](agents/any-agent.md) lists which | ||
| variables add and which replace. | ||
|
|
||
| Two caveats: | ||
|
|
||
| - `agentop exec` assumes a local Cortex: pointed at a remote stats URL | ||
| (`--cortex-stats-url`) it fetches the remote config but prints the *server's* | ||
| proxy address and CA path. Set the two variables by hand for this shape, or run | ||
| `exec` on the server itself. | ||
| - Every model call now crosses the tunnel. Fine on a LAN or over Tailscale; over a | ||
| slow WAN link, expect the latency of the link on every request. | ||
|
|
||
| ## Other targets | ||
|
|
||
| | Target | Tunnel | | ||
| |---|---| | ||
| | VM or server | `ssh -L` (above); `autossh` or SSH-over-Tailscale for a permanent one | | ||
| | Kubernetes | `kubectl port-forward`, or agentop's Namespaces → Pods picker — see [`kubernetes.md`](kubernetes.md) | | ||
| | OpenShift | one project per user, `oc port-forward` — project membership is the boundary; in a shared project, `edit` reaches every pod, so don't share | | ||
|
|
||
| On OpenShift, note that Kata / sandboxed containers silently break `port-forward` | ||
| (the runtime's implementation is a stub) — run the plain runc runtime for a pod you | ||
| want to reach this way. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: "nothing about its security model changes" holds for a single-user box, but there is one real difference from a laptop. On a shared VM or server, loopback is shared by every local account. Any other user on the host can reach the unauthenticated session API (raw prompts and completions) and the forward proxy (your credentials) without going through the tunnel. Please add a line to "The rule" saying the model assumes a host you are the only user of, or at least one you trust every account on. The same point you already make for shared OpenShift projects applies here.