Files
Fusion b40eb3e88c feat(cloudflare-tunnel): integrate Cloudflare tunnels for instance access
Backend:
- Add cloudflare_tunnel.py service for creating/deleting tunnels via Cloudflare API
- Add public_url and tunnel_id fields to ToolInstance model
- Update start_instance to create Cloudflare tunnel after container starts
- Update stop_instance to delete tunnel before stopping container
- Update delete_instance to cleanup tunnel before deletion
- Update restart_instance to recreate tunnel on restart
- Create Alembic migration 0011 for tunnel fields
- Add Cloudflare config settings (API token, zone ID, account ID, base domain)

Infrastructure:
- Add cloudflared service to docker-compose.traefik.yml
- Mount shared cloudflared_config volume between API and cloudflared containers
- Add Cloudflare env vars to API service

Frontend:
- Update instance Open button to handle both full URLs and proxy paths

The instance URL is now set to the Cloudflare tunnel public URL when available,
falling back to the API proxy path if tunnel creation fails.
2026-05-20 14:25:06 +02:00

6.5 KiB

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)
# /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?