The goals of this week
This week I decided to move my focus onto the next requirement of Nexus - Cloud hosting environment setup. If you've been following the Dev Logs, you may think this stage is a bit premature. We do not have a fully functioning website UI, yet we're moving onto website deployment already? Let me explain my reasoning...
Cloud hosting? Already?
Nexus is split into two separate parts: the frontend running Next.js, and the backend running Payload CMS. These are two different projects that communicate with each other via a REST API. This may change with the release of Payload v3, but for now, I have decided to use the microservice architecture and separate our concerns. We also need to be aware that the domains associated with these two projects will be dynamic at runtime. Though the Payload server has a fixed IP address, domains associated with it can be generated on the fly when a new tenant is added. The same goes for the frontend project - the domains for these are completely dynamic and can change at any time via the Payload dashboard.
Additionally, these two projects are running on different platforms - the frontend runs on Vercel, and backend runs on Google Cloud. I decided to split these services across platforms because of the requirements of each.
Vercel
Vercel is excellent for quickly spinning up and deploying fast, cached website frontends. I could, of course, deploy my frontend using Google Cloud as well, but that would require more fine-grained resource management, and Vercel already has an incredibly generous Pro Plan that will likely cost about the same. We already host multiple sites on Vercel, including omniux.io, and we still have plenty of resources to spare! Additionally, we can also make use of Vercel's other service offerings such as domain management, edge caching, image optimization, and edge config. It also helps that we are already using Next.js, so it's a match made in heaven.
Google Cloud
Our Payload instance is running in a Docker container on Google Cloud Run. This is a serverless instance where we pay for compute power on a per-request basis. We can spin up and down Cloud Run instances based on demand, and we can monitor this using a Load Balancer. Compared to a dedicated Google Compute instance the cost savings are enormous using this approach, and - thanks to Vercel's Edge Caching for the frontend - it doesn't matter how long a request takes to complete. The frontend web pages have already been generated and cached during build time and are re-generated and re-caching when changes are saved in Payload.
Unfortunately the servers are subject to cold-starts but the overall impact this has on boot-up performance is negligible. On top of this, Google Cloud offers a generous offering of 2-million free invocations a month. Currently,omniux.io costs us around $3 a month in network fees and this is something we hope Nexus will be capable of too.
Deployment Strategy
The deployment strategy for this project is relatively simple. We want to have 3 separate environments
- Development
- Staging
- Production
Each environment will have its own subdomain, using trunk-based deployment to automatically push all changes from the main branch into the Development environment. From there, once we are happy with code changes in Development, we will merge them into the Staging Environment. The Staging Environment will eventually act as a mimic of the production environment. Upon every deployment to staging, we will wipe the staging database and pull a copy from the production database. We will then seed the staging database with this data and check for possible conflicts. While this may sound like a large operation, it's important to note that we are unlikely to ever deal with gigabytes of data. For context, the entire database used for omniux.io (Staging and Production) comes in at under 500KB.
Once we are satisfied that everything in the staging environment works as intended, we'll take a deep breath, hold onto the nearest large solid object, and deploy our changes to production.
Targeting multiple domains
Because this is a multi-tenant application, we need to consider how the content is going to be accessed. All organizations within Nexus have domains associated with them. During data fetching, we check the domain along with the organization to process legitimate requests. For added convenience, we want our users to be able to log into their admin panel using a combination of their domain and an admin subdomain.
Mapping multiple domains is not a real challenge. Our architecture is already designed to run with dynamic system allocation, so the only logical thing to do is use a load balancer to point all traffic towards our Google Cloud Run instances!
Why use a load balancer?
A load balancer is a special name given to a piece of network software designed to manage network load from a specific IP address. Load balancers act as a kind of gate for your web servers. All external traffic gets routed to the load balancer, and from there the load balancer decides whether or not to route that incoming request to your server. It also decides WHERE to route that traffic. This gives us not only the ability to route multiple domain names to a single server (Or cloud run server in our case), but it also gives us protection from DDoS attacks.
Google includes an API which will let us add new path matches to our load balancer. So whenever a user wishes to create a new domain or subdomain, they just need to update their DNS settings to point to our load balancer, and then the load balancer will determine whether or not to route that domain to our servers.
Wrapping up
That's it for this week. Our Payload server is now configured to work in 3 separate environments, with custom domains allowing traffic to flow directly to an organization's admin panel. Next week, we'll start to look at doing a similar set up for the frontend with Vercel - including creating a way to help our users configure their DNS settings.