Shared Proxy Across Projects¶
Status¶
Implemented in #12 on 2026-04-06 as an auto-started shared proxy daemon. The user-facing guide is Shared Proxy Across Projects.
Problem Statement¶
A common usage pattern for prox is running the proxy on well-known ports (443 for HTTPS, 80 for HTTP). Users often have multiple projects on the same machine that each define their own prox.yaml, and ideally each project's domains would be served through the same proxy port.
For example:
- Project A (~/projects/auth-service/prox.yaml): auth.local.stridelabs.ai on port 443
- Project B (~/projects/audit-service/prox.yaml): audit.local.stridelabs.ai on port 443
Before the shared proxy daemon, each prox up invocation started its own independent proxy listener. If Project A already owned port 443, Project B could not bind the same port.
Decision¶
prox uses Option C: an auto-started daemon on demand.
prox upstarts the shared daemon when a proxy is configured and no daemon is running.- Each project registers its own service hostnames with the daemon.
- HTTP routes by Host header. HTTPS routes by SNI.
prox downderegisters only the current project.- The daemon stops automatically when the last project deregisters.
prox proxy status,prox proxy routes, andprox proxy stopprovide explicit inspection and control.
The daemon is per-user and per-machine. It stores state in ~/.prox/, while each project's supervisor state remains in that project's .prox/ directory.
Previous Architecture¶
- All state is per-project scoped:
.prox/prox.state,.prox/prox.pid, and.prox/prox.loglive in each project's working directory. - Each
prox upcreates its ownproxy.Servicethat binds its own HTTP/HTTPS ports. - There is no awareness of other prox instances running on the machine.
- The proxy routes requests by extracting the subdomain from the Host header and looking it up in a static
servicesmap built from the project's config at startup.
Use Cases¶
- Multiple microservices in development: Different repos/projects each define a service that should be reachable via its own subdomain, all on the same port.
- Shared base domain: All services share a base domain (e.g.,
*.local.stridelabs.ai) but are developed in separate project directories. - Independent lifecycle: Each project should be able to
prox upandprox downindependently without affecting the other projects' routes.
Options Explored¶
Option A: Central Proxy Daemon at ~/.prox/¶
A standalone, long-lived proxy process that multiple projects register with.
- An explicit command such as
prox proxy startcould start a background proxy daemon that owns port 443/80 and stores state in~/.prox/. prox upin a project detects the running proxy daemon, registers its domains/services via API, deregisters on shutdown.- The proxy daemon has its own route table that projects dynamically add to and remove from.
Pros: - Clean ownership model -- the proxy lifecycle is independent of any single project. - Multiple projects can come and go freely. - No "first project owns the port" ambiguity.
Cons: - Extra thing to manage if startup is explicit. - Requires a registration API/protocol between project instances and the daemon. - Daemon needs its own logging, status, and management commands.
Option B: First-Come-First-Served with Registration¶
The first prox up that wants a given port starts the proxy. Subsequent projects detect the existing proxy and register their routes with it via the existing API.
- No separate daemon -- the proxy lives inside whichever
prox upprocess started first. - Other projects register routes via the first instance's API.
Pros: - Simpler, no extra command or process. - Feels natural for the single-project case.
Cons:
- Fragile -- when the "owner" project does prox down, all other projects' routes die.
- Transferring proxy ownership between processes is complex and error-prone.
- The owner project's process manager and the shared proxy are coupled.
Option C: Hybrid -- Auto-Start Daemon on Demand¶
Like Option A, but the daemon auto-starts when the first prox up needs a proxy port and no daemon is running. Subsequent prox up calls detect it and register.
prox upchecks~/.prox/proxy.state-- if no proxy daemon is running, it forks one off as a separate background process.- The proxy daemon has its own PID, independent of any project.
prox proxy stop(or stopping all registered projects) brings it down.- An explicit start command was considered for manual control, but the shipped CLI relies on automatic startup.
Pros: - No extra manual step for users -- seamless experience. - Proxy daemon is independent of any project's lifecycle. - Supports both implicit and explicit management.
Cons:
- "Magic" background process that users might not realize is running.
- Need clear status/stop commands (prox proxy status, prox proxy stop).
- Auto-start logic adds complexity.
Resolved Design Questions¶
| Question | Resolution |
|---|---|
| Opt-in vs default | Shared proxy behavior is automatic for any project with a configured proxy. There is no shared config field. |
| Domain conflicts | Duplicate hostname:port registrations fail. |
| Certificate management | HTTPS certificates are managed by the shared daemon as routes are registered. |
| Stale route cleanup | The daemon tracks project PIDs and removes stale registrations for dead processes, both on a periodic sweep and inline: restarting prox up in the same directory after a crash (e.g. kill -9) detects the dead registration and replaces it immediately, without waiting for the sweep. A second registration whose owning process is still alive is still rejected. |
| Visibility and debugging | prox proxy status and prox proxy routes expose daemon state. |
| Registration protocol | Projects communicate with the daemon over the Unix socket at ~/.prox/proxy.sock. |
Caveats¶
| Caveat | Behavior |
|---|---|
| Same hostname | A second project cannot take over an existing hostname:port; registration fails. |
| Mixed protocol | A port can be HTTP or HTTPS, not both. |
| Version mismatch | A project with a different prox version cannot join the running daemon. Reset with prox proxy stop --force. |
| Sandboxed state | If ~/.prox/ is unavailable, prox falls back to a standalone per-project proxy without port sharing. |