Today I am going to talk about how I implemented custom domains support in DynaSite. See the previous post where I talk about my plans to relaunch DynaBlogger as DynaSite, with new features including an AI-enhanced page builder.
Since I launched DynaBlogger, one of the most common questions I get asked is "how did you implement custom domains?"
The main reason I get asked this is TLS/SSL and certificates. To implement custom domains in your SaaS you need to somehow provision certificates for these domains.
Also the way you provision certificates must be able to scale regardless of how many customers (and thus how many custom domains) your SaaS needs to handle. This is a problem even with popular PaaS like Heroku because there's a limit with the number of custom domains that they can support for a single app. In some cases you can contact support to raise these limits but not sure up to what extent.
An alternative is to build something yourself based on Let's Encrypt to be able to offer certificates for free, especially if you manage the apps on your servers, but it can get complicated as your infrastructure grows and you need to manage and use certificates with many servers and load balancers.
The way the basic functionality works is quite simple:
user adds a custom domain to their blog
the app creates an ingress resource in Kuberrnetes for that domain with support for unencrypted traffic on port 80
the app exposes a special, unique and difficult to guess path at http://domain.com/verification/<some long UUID>
the app polls every few seconds that path and checks the response. If the response contains a matching key, then it flags the domain as verified because it means that the domain is correctly pointing to our app. The domain remains in pending verification state in the UI until this condition is met
once the domain is verified, the ingress resource is "upgraded" with support for encrypted connections, meaning that it adds to the ingress resource the required annotation to trigger a certificate issuance request to Let's Encrypt via cert-manager
once the certificate is issued successfully, the domain is marked as ready and the app can start serving the user's content from their custom domain
when a custom domain is removed by the user, the relevant ingress resource is also deleted
You might ask, why perform a verification ourselves if Let's Encrypt already does this? The reason is that there are some rate limits that take into account also verification failures. So before even letting cert-manger request a certificate, I make sure that the domain is in fact pointing to my app.
I might also add that to make the verification more robust, and ensure that the user who is currently trying to use the domain owns it, you might want to use some sort of DNS based verification instead, which can help prevent a form of attacks called "subdomain takeover".
This process is quite simple and doesn't require much coding and can scale a lot with a Kubernetes cluster regardless of how many nodes etc you have; once you reach the limit of how many virtual hosts the ingress controller can handle, you can scale with multiple ingress controller, and so on.
The next question I get asked often is, "how do I do implement this stuff if I don't use Kubernetes?"
These past few evenings I have started to work on the DynaBlogger relaunch as DynaSite, but until the actual launch I won't be setting up a Kubernetes cluster and will use a single server to cut costs until proper scaling is required.
At the moment I am using Dokku on this single server to host the app, and I had to figure out how to handle custom domains while I keep this simpler setup. I evaluated a few options, and then finally settled on Cloudflare for SaaS since I already use Cloudflare and it's a brilliantly simple solution. I am probably going to keep this even when I launch since I really like it as a solution and doesn't require my users to use Cloudflare for their domains. 100 hostnames are free and after that it's just $0.10 per hostname.
Cloudflare for SaaS
If you don't have a Cloudflare account yet, you'll need to create one (there's a free plan) and migrate your domain's DNS configuration to Cloudflare.
Once the domain is ready, you need to configure a DNS record pointing to your SaaS. Cloudflare will forward any traffic for custom hostnames to the hostname you configure in this record. Make sure the proxy is enabled for this record (orange cloud).
cloudflare-proxy.png5.1 KB
Next, head to
cloudflare-custom-hostnames-01.png20 KB
and enable the feature, then add a "fallback origin":
fallback-origin.png82.6 KB
Once it's active, you can start adding custom domains for your users. For simplicity, I use the HTTP verification method:
new-hostname.png186 KB
Give it a few moments, and then the hostname will appear as ready:
hostname-ready.png9.63 KB
Dokku
To complete the configuration, we need to remove the default site virtual host configured in Nginx:
And create a new virtual host that points to your SaaS app, which will be a "catch-all" for domains not directly configured here. Create this file at /etc/nginx/conf.d/00-default-vhost.conf:
Of course replace "dynasite" with the name of your app, and "3000" with its correct container port.
Finally, reload nginx:
sudo nginx -t
sudo nginx -s reload
That's it!
Wrapping up
As you can see setting up custom domains for your SaaS is ridiculously easy (and pretty cheap too) using Cloudflare. You don't need to worry about scale or anything really. Like I mentioned I might keep this solution instead of using cert-manager again because it's portable and I like it a lot. I didn't show here how to you use the API to create custom hostnames in Cloudflare, but it's very easy and there are libraries for many programming languages. Let me know in the comments what you think!
I am a passionate developer based in Espoo, Finland, where I work as Senior Backend Developer for event management platform Brella.
I also started again "building in public", and the first project is DynaSite, a static site and blog hosting platform with an a visual page builder and cool AI features.
My roles as architect, coder and technology enthusiast overlap each other here on this web log.
Unless otherwise noted, the views expressed on these pages are mine alone and not those of my employer.