How to run a multi-domain and multi-stage SaaS web application?

Our idea is to run multiple stages of an app as multiple Fly.io apps like: alpha-my-app.fly.dev, beta-my-app.fly.dev and stable-my-app.fly.dev.

With routing table of project domains mapped to those stages - so we can pick and change which stage any domain currently runs on.

const router = {
	"project1.my-saas.com": "stable-my-app",
	"project3.my-saas.com": "stable-my-app",
	"project4.my-saas.com": "beta-my-app",
	"project5.my-saas.com": "alpha-my-app",
}

What we need is of course possible by changing CNAME, but we would very much like to minimize the need to change DNS.

I imagine we can deploy a gateway/proxy/router app that would hold the routing table and then use Dynamic Request Routing with fly-reply: app=beta-my-app header.

This works, but Dynamic Request Routing is limited to 1MB requests which at the moment sounds reasonable, but in the future this might become painful. Also not sure how optimal this flow is around traffic pricing etc.

// Using Dynamic Request Routing this works too:
Bun.serve({
	async fetch(req) {
		const url = new URL(req.url)

		const rewriteToApp = router[url.hostname]

		if (found) {
			return new Response('', {
				headers: {
					'fly-replay': `app=${rewriteToApp}`,
				},
			})
		}

		return new Response(`Invalid hostname ${url.hostname}`)
	},
})

console.log('Running', server.hostname, server.port)

Ideally we could create aliases for fly.io apps - that would allow me to configure each project CNAME to its own fly.io app alias. And then just switch which alias belongs to which app.

# my imagined commands would look like these:
flyctl alias add project1 --app=stable-my-app
flyctl alias add project2 --app=alpha-my-app

Other topic I found in docs is Flycast, but I am not very sure how that works.

Do you have any recommendations, notes or tips?

Thank you.

1 Like

Hi,

Interesting …

I don’t know of any alias command :thinking:.

The app’s name is used in the app-name.fly.dev hostname. So it needs to be unique. The certificate (linking the custom domain) is linked to the app. So yes, DNS would be an idea however understandably you don’t want to update that (perhaps because of clients caching DNS records, even if you have a short TTL).

I did wonder if you could run your own custom DNS, with a custom backend e.g PowerDNS Authoritative Nameserver — PowerDNS Authoritative Server documentation with e,g MySQL/Postgres. So you would update the stage per-hostname in the database, and that would then return the new DNS record.

Else there’s the option of using an external service to be the proxy to route a request to an origin Fly app e.g

… which seems to be what you are doing with your Bun example (without the Fly replay magic). But … either way, there would need to be some form of lookup/table to match a hostname to a stage.

Deploying a Worker (or deploying a small Fly proxy app) on any change to the routing would mean you could hard-code the routing. Rather than editing DNS.

Or if you didn’t want to hard-code in the routing table, you could have that proxy/app look up the routing in some form of external database. Maybe a local SQLite. Or some other database that supports a http API that you could call fetch() with Bun or from a Worker. Quite a few now do e.g Supabase, Planetscale, Neon (Neon serverless driver - Neon Docs). Then once that works, cache that hostname → stage result for X seconds to avoid a database lookup on every request (as your app/stage is probably not going to change very often).

aah aliases would be fantastic, asked for something similar here

From How To to Questions / Help

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.