Skip to main content

Running without a domain

Vambora ships without buying a custom domain. Everything runs on free hostnames with real HTTPS. Buying vambora.app later is a config change, not a code change.

What runs where

SurfaceNow (free)Later (domain bought)
Web PWAhttps://vambora-web.pages.dev (Cloudflare Pages)https://vambora.app
Docshttps://vambora-docs.pages.dev (Cloudflare Pages)https://docs.vambora.app
APIhttps://api-<vm-ip>.sslip.io (Oracle VM + Coolify)https://api.vambora.app
Map tilesMapTiler style URL (Path A)unchanged, or Protomaps/R2 (Path B)

Why this works

  • Cloudflare Pages gives every project a *.pages.dev subdomain with automatic HTTPS — no domain, no cert work.
  • sslip.io is a free public DNS service: api-203-0-113-7.sslip.io resolves to 203.0.113.7. No signup, no account, no DNS records. Because it's a real resolvable hostname, Coolify's built-in proxy obtains a real Let's Encrypt certificate for it over HTTP-01. Browsers see valid HTTPS, so the pages.dev web app can call the API (no mixed-content, no self-signed warnings).
  • The backend CORS allowlist is env-driven (CORS_ALLOW_ORIGINS), so the pages.dev origin is allowed without editing code.

sslip.io uses dashes or dots: api-203-0-113-7.sslip.io or api.203.0.113.7.sslip.io both resolve to 203.0.113.7. Use the dashed form — some TLS tooling dislikes all-numeric dotted labels.

The two settings that make it work

  • Web (Cloudflare Pages env var): NEXT_PUBLIC_API_BASE_URL=https://api-<vm-ip>.sslip.io
  • Backend (Coolify env var): ENVIRONMENT=production and CORS_ALLOW_ORIGINS=https://vambora-web.pages.dev

If the web shows data but the browser console logs CORS errors, these two are out of sync — that's the only failure mode unique to this setup.

Deploy to Cloudflare Pages (no domain, no token)

Method: the Cloudflare dashboard "Connect to Git" integration. Cloudflare builds and redeploys both sites on every push to main. No API token, no Account ID, no GitHub secrets.

The project is a single monorepo (matheus-of-freitas/vambora, branch main) with web/ + docs/ + backend/. Create two Pages projects on the same repo, each with a different Root directory. (Commit and push the repo first — Connect-to-Git needs commits on main.)

  1. dash.cloudflare.com → Workers & Pages → Create → Pages → Connect to Git → authorise GitHub → select repo vambora.
  2. web project:
    • Project name: vambora-web
    • Production branch: main
    • Root directory: web
    • Framework preset: Next.js
    • Build command: npx @cloudflare/next-on-pages@1
    • Build output directory: .vercel/output/static
    • The Next.js App Router has on-demand SSR routes (/lines/[shortName], /stops/[stopId]), so it deploys via Cloudflare's official adapter, not as a plain static site. web/wrangler.toml (committed) carries the required compatibility_flags = ["nodejs_compat"]; if the dashboard doesn't apply it, also set nodejs_compat under Settings → Functions → Compatibility flags for Production + Preview.
    • Settings → Variables and Secrets (Production + Preview):
      • NEXT_PUBLIC_API_BASE_URL = https://api-<vm-ip>.sslip.io
      • NEXT_PUBLIC_MAP_STYLE = your MapTiler style URL (https://api.maptiler.com/maps/dataviz-dark/style.json?key=KEY)
      • NODE_VERSION = 20
    • The MapTiler key lives only here and in local web/.env.local — never committed.
  3. docs project:
    • Project name: vambora-docs
    • Production branch: main
    • Root directory: docs
    • Framework preset: Docusaurus
    • Build command: pnpm build
    • Build output directory: build
    • NODE_VERSION = 20. No app variables.
  4. (Optional, monorepo hygiene) On each project set Build watch paths to web/* / docs/* so a docs-only push doesn't rebuild web and vice versa.
  5. Push to main → both build → live at https://vambora-web.pages.dev and https://vambora-docs.pages.dev (HTTPS automatic).

What you will NOT see (and shouldn't look for)

Because there's no domain, there's no Cloudflare zone, so:

  • No "DNS" app / DNS records. The per-domain DNS editor only appears inside a zone. The dashboard's "DNS Firewall", "DNS Settings", "DNS Views" are different account-level products — not relevant here.
  • No "Zone" API-token permission. With zero zones there's nothing to scope it to. You don't create an API token at all for this method.

Both are expected and only become relevant in the next section.

Switching to a real domain later (no code changes)

  1. Buy vambora.app (Cloudflare Registrar is cheapest, ~$14/yr .app).
  2. Cloudflare DNS: vambora.app/docs.vambora.app → the two Pages projects; api.vambora.app → an A record to the Oracle VM public IP.
  3. In Coolify, set the backend service domain to https://api.vambora.app (it re-issues Let's Encrypt for the new name automatically).
  4. Change exactly two env vars and redeploy:
    • web NEXT_PUBLIC_API_BASE_URL=https://api.vambora.app
    • backend CORS_ALLOW_ORIGINS=https://vambora.app (add the pages.dev origin too during the cutover so both work in parallel).

No source edits. The sslip.io URL keeps working until you remove it.