Skip to content

Cloudflare Tunnel

Gets api.chemassist-occ.com to the droplet's nginx container without opening any inbound port on the box besides SSH.

Model: remotely managed

Hostname routing lives in the Cloudflare dashboard, not a local config.yml on the droplet. The cloudflared container only needs a TUNNEL_TOKEN; everything else (which hostname maps to which origin) is configured once in Zero Trust and can be changed without touching the droplet.

Setup

  1. Cloudflare dashboard → Zero Trust → Networks → Tunnels → Create a tunnel → name it (e.g. chem-assistant-api) → choose "Docker" as the connector — copy the token it gives you (the long string after --token in the sample command). That's TUNNEL_TOKEN.
  2. Same tunnel → Public Hostname tab → Add a hostname:
    • Subdomain: api, Domain: chemassist-occ.com
    • Service: HTTP, URL: nginx:80 (the container name inside the compose network, not localhostcloudflared and nginx share the compose network, not the host's).
  3. Paste TUNNEL_TOKEN into /opt/csa/.env and bring the cloudflared service up (it's already in docker-compose.prod.yml).
  4. Verify: curl https://api.chemassist-occ.com/ should return the API's health JSON.

Gotchas

  • Never run cloudflared service install <token> on a laptop or any second machine with the same token. It registers a second connector on the same tunnel, and requests round-robin between the droplet and whatever else has the token — confusing intermittent 502s that look like a droplet problem but aren't.
  • cloudflared has no depends_on in the compose file on purpose — gating it on postgres/redis/api being healthy would block bringing up infra-only on a fresh droplet before .env is fully written.
  • The tunnel terminates TLS at Cloudflare's edge. nginx inside the compose network serves plain HTTP on port 80 — that's correct, not a missing cert.