Add API proxy endpoint so users can access running tool instances
through the backend API instead of internal Docker network.
Backend:
- Add container_name field to ToolInstance model
- Create /instances/{id}/proxy/{path:path} endpoint with ownership checks
- Proxy HTTP requests to containers via docker network using container names
- Support all HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
- Store proxy URL in instance.url instead of localhost
- Add Alembic migration 0007 for container_name column
- Add get_container_name() utility to docker.py
Frontend:
- Update Open button to use full proxy URL (API_BASE_URL + instance.url)
Closes instance-proxy OpenSpec change.
3.0 KiB
Context
Currently, tool instances run as Docker containers on the internal Docker network. The backend stores their URL as http://localhost:{port}, which is only accessible from inside the API container. Users clicking "Open" in the frontend get a 404 because their browser can't reach the internal container.
The API and containers share a Docker network, so the API can reach containers by their container name or IP.
Goals / Non-Goals
Goals:
- Users can access running tool instances through the API via HTTPS
- Proxy enforces ownership (only instance owner can access)
- Support both HTTP and WebSocket traffic
- Minimal latency overhead
- Works with existing Docker setup
Non-Goals:
- Public URLs / custom domains for instances (that's Option 2/3)
- Load balancing across multiple instances
- Advanced path rewriting (just pass-through)
Decisions
Proxy via FastAPI route (not separate service)
Decision: Implement proxying as a FastAPI endpoint using httpx for async forwarding.
Rationale:
- Keeps everything in one deployable unit
- Easy access to existing auth dependencies (
get_current_user_id) - Can reuse existing session cookie auth
- No extra infrastructure needed
Alternative considered: Separate nginx/traefik proxy service
- Rejected: adds operational complexity for a single feature
Use container name for internal routing
Decision: Store container name in ToolInstance model and route to http://{container_name}:{port}
Rationale:
- Container names are stable and DNS-resolvable within Docker network
- More reliable than IPs which can change
- Already using container names in docker.py
Path: /instances/{id}/proxy/{path:path}
Decision: All proxied traffic goes through /instances/{id}/proxy/*
Rationale:
- Clear URL structure
- Easy to apply auth middleware
path:pathcaptures everything after/proxy/
WebSocket upgrade handling
Decision: Support WebSocket upgrade by inspecting the Upgrade: websocket header and establishing a bidirectional pipe.
Rationale:
- code-server and jupyter use WebSockets for real-time features
- FastAPI doesn't natively support proxying WebSockets, but we can use
starlette.websocketsto handle the upgrade
Risks / Trade-offs
[Risk] API becomes bandwidth bottleneck for all instance traffic → Mitigation: Document this limitation. Future migration to Option 2 (Traefik labels) possible.
[Risk] Container name collision → Mitigation: Instance names already include UUID suffix, collision probability is negligible.
[Risk] Large file uploads/downloads through proxy → Mitigation: Use streaming response in httpx. Monitor memory usage.
Migration Plan
- Deploy backend changes (proxy endpoint + model updates)
- Update frontend links to use proxy URL
- Test with code-server instance
- Monitor API performance
Open Questions
- Should we add rate limiting to the proxy endpoint?
- Do we need to rewrite response headers (Location, Set-Cookie)?