Skip to content

Cloud Deployment

Sergei Emelianov edited this page Aug 12, 2026 · 1 revision

Running the whole stack in the cloud

Most people run career-ops on their own laptop. But the pipeline is at its best when it is always on — scanning boards while you sleep, keeping the tracker fresh, reachable in a browser from any device. This page is the end-to-end recipe for putting the whole stack on a small cloud server: the parent career-ops pipeline, the career-ops-ui viewer, and the engine that runs the AI — either your Claude subscription (through the Claude Code CLI) or a local Hermes gateway.

This is operator how-to, not an app feature. The complete, security-focused checklist is docs/integrations/HERMES.md; the in-app version is Help §31.

The three moving parts

Part What it is Where it lives
career-ops (parent) The AI job-search pipeline. Owns cv.md, config/, reports/, portals.yml. Driven by an agent CLI. the repo root
career-ops-ui (this app) A read-mostly web viewer that surfaces the same files in a browser; writes back only on explicit actions. career-ops/web-ui/
the engine Whatever answers the AI prompts — a Claude subscription, a local Hermes gateway, or provider API keys. on the box (CLI / gateway) or a remote API

Two of the three are identical on a server and on your laptop — only the engine choice and the network exposure change.

1. Provision and install

  • A small VPS (1 vCPU / 1 GB RAM is plenty for the viewer), current Linux.
  • Node ≥ 18 (22.5+ recommended — it enables the parent's SQLite tracker index) and git.
  • Install exactly like a local install: clone the parent career-ops, then clone this repo inside it as career-ops/web-ui/.
  • Put provider keys in the parent's .env (never commit it — .env / .env.* are gitignored; start from .env.example).
  • Run the viewer with npm start, bound to 127.0.0.1 — you expose it through a proxy, not directly.

2. Pick your engine

career-ops is CLI-agnostic, so there are three honest options:

  • Your Claude subscription — install the Claude Code CLI on the box and claude login with your Pro/Max plan. The parent's agent runs then use your subscription (no per-token API bill).
  • Hermes — run hermes gateway on the same box (OpenAI-compatible API at http://127.0.0.1:8642/v1) and set HERMES_API_KEY in App settings. career-ops-ui's live evaluations route through it (last in the auto provider order).
  • API keys — set any of the seven providers (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models → Hermes) in the parent .env, and the ⚡ live actions work headlessly.

You can mix them: a Claude subscription for the parent's heavy agent work, and a cheap or local provider for the viewer's quick evaluations.

3. Expose it safely

Moving off 127.0.0.1 means the safety loopback gave you for free must now be built explicitly — the code is identical; only the exposure changes.

  • Keep the app on loopback; put a reverse proxy (nginx / Caddy) in front that terminates HTTPS (Let's Encrypt / automatic TLS) and forwards to 127.0.0.1:4317.
  • Run it under systemd or pm2 as a dedicated non-root user, Restart=on-failure.
  • Put authentication in front — the app has no login of its own, so the proxy (basic-auth, SSO forward-auth, or a private network / VPN) is what keeps strangers out.
  • When you set HOST=0.0.0.0 so the proxy can reach it, the built-in hardening that was a no-op on loopback switches on and becomes load-bearing: the LLM rate-limit, the safeGet DNS-rebind defense, and path-name sanitizing.

Invariants that must survive the move (do not relax)

  • CSP — no inline scripts, frame-ancestors 'none'. The proxy must not strip these headers.
  • SSRF guard — every user-supplied URL fetch goes through isValidJobUrl() + safeGet (no loopback, no file://).
  • Markdown/XSS boundarystripDangerousMarkdown() server-side + escape-first UI.md() client-side.
  • No secrets in logs — provider keys, tokens, PII never logged; a remote box with shipped logs makes this more important.
  • Parent read-only contract — the server only reads cv.md / config/ / reports/ and writes on explicit actions.

See also

Clone this wiki locally