chore: archive superseded cloudflare-tunnel-instances OpenSpec change
This change proposed using Cloudflare API for persistent tunnels. Superseded by temporary tunnel approach using 'cloudflared tunnel --url' which requires no API tokens, account IDs, or DNS configuration.
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-20
|
||||
@@ -0,0 +1,142 @@
|
||||
## Context
|
||||
|
||||
Currently, tool instances are exposed via an API proxy endpoint that forwards requests from `/instances/{id}/proxy/` to the internal Docker container. This creates latency, adds load to the API service, and doesn't support WebSocket features well. Cloudflare Tunnel offers a better architecture where each instance gets its own HTTPS subdomain.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Each running tool instance gets a unique public HTTPS subdomain
|
||||
- No manual DNS or reverse proxy configuration per instance
|
||||
- Automatic cleanup when instances are stopped or deleted
|
||||
- Support for WebSocket and real-time features (code-server terminal, jupyter kernels)
|
||||
- Minimal latency compared to API proxy approach
|
||||
|
||||
**Non-Goals:**
|
||||
- Custom domains per instance (use Cloudflare zone's wildcard)
|
||||
- Advanced tunnel features (load balancing, failover, ingress rules)
|
||||
- Replacing Traefik for the main app (API + frontend)
|
||||
- Supporting non-HTTP protocols (TCP/UDP raw tunneling)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Cloudflare API vs cloudflared CLI
|
||||
|
||||
**Decision:** Use the Cloudflare REST API to create/manage tunnels, not the `cloudflared` CLI.
|
||||
|
||||
**Rationale:**
|
||||
- The API gives us programmatic control without parsing CLI output
|
||||
- We can use `httpx` (already a dependency) instead of subprocess calls
|
||||
- Easier to test and mock
|
||||
|
||||
**Alternative considered:** Running `cloudflared tunnel create` via subprocess
|
||||
- Rejected: Fragile, harder to test, requires cloudflared binary in API container
|
||||
|
||||
### Architecture: cloudflared as a separate container
|
||||
|
||||
**Decision:** Run `cloudflared` as a standalone Docker service that connects to Cloudflare and routes traffic.
|
||||
|
||||
**Rationale:**
|
||||
- Separation of concerns: API manages tunnels, cloudflared handles connectivity
|
||||
- The cloudflared container can access the Docker internal network where instances run
|
||||
- Easier to scale/restart independently
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Cloudflare Edge │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│ HTTPS
|
||||
┌──────────────────────▼──────────────────────────────────────┐
|
||||
│ cloudflared container │
|
||||
│ (connects to Cloudflare, receives traffic for *.zone) │
|
||||
└──────────┬──────────────────────────────────────────────────┘
|
||||
│ Docker network
|
||||
┌──────────▼──────────────────────────────────────────────────┐
|
||||
│ code-server container:8443 jupyter container:8888 │
|
||||
│ (tool instances on Docker network with DNS names) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Subdomain naming
|
||||
|
||||
**Decision:** Use `instance-{short-uuid}.{zone}` format (e.g., `instance-a1b2c3d4.headquarter.commumedia.org`)
|
||||
|
||||
**Rationale:**
|
||||
- Predictable and URL-safe
|
||||
- Short enough to be readable
|
||||
- UUID ensures uniqueness without exposing internal IDs
|
||||
|
||||
### Tunnel lifecycle
|
||||
|
||||
**Decision:** Create tunnel on instance start, delete on instance stop/delete.
|
||||
|
||||
**Flow:**
|
||||
1. User clicks "Start"
|
||||
2. Backend creates Cloudflare tunnel via API
|
||||
3. Backend creates DNS CNAME record: `instance-abc123` → `{tunnel-id}.cfargotunnel.com`
|
||||
4. Backend stores `tunnel_id` and `public_url` in ToolInstance
|
||||
5. cloudflared container routes traffic to container:port
|
||||
6. On stop: delete DNS record, delete tunnel
|
||||
|
||||
### cloudflared configuration
|
||||
|
||||
**Decision:** Use a single cloudflared container with dynamic config file updates.
|
||||
|
||||
**Approach:**
|
||||
- The cloudflared container reads an `config.yml` file mounted as a volume
|
||||
- The API writes ingress rules to this file when instances start/stop
|
||||
- cloudflared automatically reloads the config (or we restart the container)
|
||||
|
||||
```yaml
|
||||
# /etc/cloudflared/config.yml
|
||||
tunnel: {tunnel-token}
|
||||
credentials-file: /etc/cloudflared/credentials.json
|
||||
ingress:
|
||||
- hostname: instance-abc123.headquarter.commumedia.org
|
||||
service: http://code-server-repo-abc123:8443
|
||||
- hostname: instance-xyz789.headquarter.commumedia.org
|
||||
service: http://jupyter-repo-def:8888
|
||||
- service: http_status:404
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
**Decision:** Cloudflare tunnels provide HTTPS but do NOT handle app-level auth. Tool instances without built-in auth (like code-server) will be publicly accessible.
|
||||
|
||||
**Rationale:**
|
||||
- Cloudflare Access could add auth, but adds complexity
|
||||
- Many tools (code-server) have their own password/auth mechanisms
|
||||
- Users should configure tool-level auth via ToolConfig
|
||||
|
||||
**Mitigation:** Document that users must configure tool passwords via ToolConfig (e.g., `PASSWORD` env for code-server).
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk]** Cloudflare API rate limits (1200 requests/5 min)
|
||||
→ **Mitigation:** Tunnel creation is infrequent (user-initiated), unlikely to hit limits
|
||||
|
||||
**[Risk]** cloudflared container becomes a single point of failure
|
||||
→ **Mitigation:** It's stateless; can be restarted quickly. All instances share one cloudflared.
|
||||
|
||||
**[Risk]** Subdomain enumeration exposes running instances
|
||||
→ **Mitigation:** UUID-based names are hard to guess. Consider adding Cloudflare Access in future.
|
||||
|
||||
**[Risk]** cloudflared config file updates require container restart
|
||||
→ **Mitigation:** Investigate `cloudflared --no-autoupdate` with config watch, or accept brief restart
|
||||
|
||||
**[Risk]** Tool instances publicly accessible without auth
|
||||
→ **Mitigation:** Document security best practices, recommend setting tool passwords
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Deploy cloudflared container with base config
|
||||
2. Add Cloudflare env vars to API container
|
||||
3. Deploy backend changes (tunnel service, updated lifecycle)
|
||||
4. Deploy frontend changes (use public_url instead of proxy)
|
||||
5. Test with code-server instance
|
||||
6. Remove old proxy endpoint code
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should we add Cloudflare Access (Zero Trust) to protect instances?
|
||||
- Do we need to support custom subdomains (e.g., `myproject.headquarter.commumedia.org`)?
|
||||
- Should we keep the proxy endpoint as a fallback?
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
## Why
|
||||
|
||||
The current approach of proxying tool instances through the backend API is fragile and creates a bottleneck. Every HTTP request and WebSocket connection to a tool instance (code-server, jupyter, etc.) must pass through the FastAPI application, adding latency and consuming API resources. Cloudflare Tunnel provides a robust alternative: each instance gets its own public subdomain with automatic HTTPS, without exposing ports or requiring complex reverse proxy rules.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Replace the API proxy endpoint (`/instances/{id}/proxy/`) with Cloudflare Tunnel integration
|
||||
- Run a `cloudflared` container alongside the API that manages tunnels programmatically via the Cloudflare API
|
||||
- When a tool instance starts, create a unique Cloudflare Tunnel and DNS record pointing to the instance's internal container name and port
|
||||
- Store the public URL (e.g., `https://instance-abc123.headquarter.commumedia.org`) in the ToolInstance model
|
||||
- Update the frontend "Open" button to use the Cloudflare URL instead of the proxy path
|
||||
- Remove the proxy endpoint and related code (instance_proxy.py)
|
||||
- **BREAKING**: The `/instances/{id}/proxy/{path:path}` endpoint will be removed
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `cloudflare-tunnel-management`: Creating, deleting, and managing Cloudflare tunnels for tool instances via the Cloudflare API
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `instance-proxy`: The current proxy-based approach will be replaced by Cloudflare tunnels. The requirement that "The API SHALL expose an endpoint that forwards HTTP requests" is replaced by "The system SHALL provide a public URL for each running instance."
|
||||
|
||||
## Impact
|
||||
|
||||
- Backend: New Cloudflare tunnel service, updated instance lifecycle (create tunnel on start, delete on stop), removed proxy code
|
||||
- Frontend: Update "Open" links to use public Cloudflare URLs
|
||||
- Infrastructure: New `cloudflared` Docker service, Cloudflare API token required
|
||||
- Environment: New env vars: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_ZONE_ID`
|
||||
- Docker: Cloudflared container must be on the same network as tool instances
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: System creates Cloudflare tunnel on instance start
|
||||
When a tool instance is started, the system SHALL create a Cloudflare tunnel and DNS record to expose it publicly.
|
||||
|
||||
#### Scenario: Start instance creates tunnel
|
||||
- **WHEN** a user starts a tool instance
|
||||
- **THEN** the system calls the Cloudflare API to create a tunnel
|
||||
- **AND** creates a CNAME DNS record for `instance-{id}.{zone}`
|
||||
- **AND** stores the tunnel ID and public URL in the database
|
||||
|
||||
#### Scenario: Tunnel points to correct container
|
||||
- **WHEN** a tunnel is created for an instance
|
||||
- **THEN** the tunnel ingress rule maps the subdomain to the container's internal DNS name and port
|
||||
|
||||
### Requirement: System deletes Cloudflare tunnel on instance stop
|
||||
When a tool instance is stopped or deleted, the system SHALL clean up the associated Cloudflare tunnel and DNS record.
|
||||
|
||||
#### Scenario: Stop instance deletes tunnel
|
||||
- **WHEN** a user stops a running instance
|
||||
- **THEN** the system deletes the DNS record
|
||||
- **AND** deletes the Cloudflare tunnel
|
||||
|
||||
#### Scenario: Delete instance cleans up tunnel
|
||||
- **WHEN** a user deletes an instance
|
||||
- **AND** the instance has an active tunnel
|
||||
- **THEN** the system deletes both the DNS record and the tunnel
|
||||
|
||||
### Requirement: Frontend uses public URL for instance access
|
||||
The frontend SHALL display and link to the public Cloudflare URL for running instances.
|
||||
|
||||
#### Scenario: Open button uses public URL
|
||||
- **WHEN** a user views a running instance
|
||||
- **THEN** the "Open" button links to the instance's public URL
|
||||
- **AND** the URL opens in a new tab
|
||||
|
||||
#### Scenario: Session list shows public URL
|
||||
- **WHEN** a user views their sessions
|
||||
- **THEN** each running session displays its public URL
|
||||
|
||||
### Requirement: Only instance owner can start/stop/delete tunnels
|
||||
The system SHALL verify that only the instance owner can trigger tunnel creation or deletion.
|
||||
|
||||
#### Scenario: Owner starts instance
|
||||
- **WHEN** the instance owner clicks "Start"
|
||||
- **THEN** the tunnel is created successfully
|
||||
|
||||
#### Scenario: Non-owner attempts to start
|
||||
- **WHEN** a non-owner attempts to start an instance
|
||||
- **THEN** the request returns 403 Forbidden
|
||||
@@ -0,0 +1,45 @@
|
||||
## 1. Infrastructure Setup
|
||||
|
||||
- [ ] 1.1 Add cloudflared service to docker-compose.traefik.yml
|
||||
- [ ] 1.2 Create cloudflared config directory and base config
|
||||
- [ ] 1.3 Add Cloudflare env vars (API token, account ID, zone ID) to .env.example
|
||||
- [ ] 1.4 Mount shared config volume between API and cloudflared containers
|
||||
|
||||
## 2. Backend - Cloudflare Tunnel Service
|
||||
|
||||
- [ ] 2.1 Create `src/services/cloudflare_tunnel.py` with tunnel CRUD operations
|
||||
- [ ] 2.2 Implement `create_tunnel(instance_name, container_name, port)` function
|
||||
- [ ] 2.3 Implement `delete_tunnel(tunnel_id)` function
|
||||
- [ ] 2.4 Implement `update_cloudflared_config()` to rewrite config.yml
|
||||
- [ ] 2.5 Add Cloudflare API token validation on startup
|
||||
|
||||
## 3. Backend - Instance Lifecycle Updates
|
||||
|
||||
- [ ] 3.1 Update ToolInstance model: add `tunnel_id` and `public_url` fields
|
||||
- [ ] 3.2 Create Alembic migration for new fields
|
||||
- [ ] 3.3 Update `start_instance` to create tunnel and store public_url
|
||||
- [ ] 3.4 Update `stop_instance` to delete tunnel and DNS record
|
||||
- [ ] 3.5 Update `delete_instance` to ensure tunnel cleanup
|
||||
- [ ] 3.6 Update `get_user_sessions` to include `public_url`
|
||||
|
||||
## 4. Backend - Cleanup
|
||||
|
||||
- [ ] 4.1 Remove `instance_proxy.py` router
|
||||
- [ ] 4.2 Remove proxy route registration from `main.py`
|
||||
- [ ] 4.3 Remove `default_port` from ToolType (no longer needed)
|
||||
- [ ] 4.4 Clean up any proxy-related code
|
||||
|
||||
## 5. Frontend Updates
|
||||
|
||||
- [ ] 5.1 Update Session interface to include `public_url`
|
||||
- [ ] 5.2 Update InstanceList "Open" button to use `public_url`
|
||||
- [ ] 5.3 Update SessionsPage "Open" button to use `public_url`
|
||||
- [ ] 5.4 Remove proxy URL construction logic
|
||||
|
||||
## 6. Testing and Deployment
|
||||
|
||||
- [ ] 6.1 Test tunnel creation with code-server instance
|
||||
- [ ] 6.2 Test tunnel deletion on instance stop
|
||||
- [ ] 6.3 Verify HTTPS and WebSocket support
|
||||
- [ ] 6.4 Run quality gates (ruff, mypy, typecheck, lint, build)
|
||||
- [ ] 6.5 Deploy and test end-to-end
|
||||
Reference in New Issue
Block a user