Skip to content

Multi-Tenancy

Multi-tenancy is a pattern in which multiple brands may be served from the same codebase. It can be used to quickly onboard a new brand, or ‘tenant’, via a new configuration, as a fast route to scale.


Custom Domains

Altitude allows multiple custom domains to be attached to a single site, giving the feel of a separately hosted site per-tenant.

For example www.allsole.com and www.biossance.com point to the same Altitude deployment.

Tenant Configuration

There are certain properties which will want to vary between tenants to give then a distinct feel, such as:

  • Styling
  • Feature flags
  • API urls, reCaptcha keys etc.

A solution to achieving this is for your application to read these properties from a configuration object which can vary per-tenant.

Key-Value Stores

Altitude KV Stores can be used as a store for tenant configuration, which can be read at request time.

If your application is using the Astro integration package the KV binding will occur at runtime from within the integration otherwise refer to your appropriate framework guide on how to set this up.

Framework Guides

Determining the Tenant

In order to read the correct tenant config in middleware, the tenant for the incoming request first needs to be determined.

Production Environments

A mapping between custom domain and tenant config key can be used to determine the correct key per request. Production environments can use X-Forwarded-Host which will be provided as a request header from Fastly.

Local and UAT Environments

For cases where custom domains are not attached, such as localhost and on deployments to other Altitude development environments, a custom HTTP header may be used to specify tenant per request. A browser extension such as ModHeader is convenient for fast tenant switching in this case.

Multi-tenancy Stylesheets

When sharing a codebase with multiple tenants, being able to still differentiate branding colours and typography is essential. However it’s equally essential to ensure that the codebase is DRY and you’re not building stylesheets when not necessary.

Tailwind

By utilising Tailwind Themes alongside a combination of data attributes and CSS variables you can achieve a pattern of having one shared UI but with tenant specific CSS.

Example:

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html[data-theme="default"] {
--color-esther: 34, 39, 46;
--color-maximus: 45, 51, 59;
--color-linx: 55, 62, 71;
}
html[data-theme="neon"] {
--color-esther: 20, 61, 42;
--color-maximus: 13, 82, 66;
--color-linx: 20, 82, 11;
}
}

Within your HTML you then switch to the relevant theme:

<html data-theme="neon"></html>
Alternative: DaisyUI + Tailwind

DaisyUI offers a multi-theme configuration out of the box. This can help achieve the capability of creating multiple themes which can be used for creating variety across tenants.

DaisyUI Themes DaisyUI components

Astro Integration

Applications using the Astro Integration can leverage built in mapping to resolve configs to the correct tenant. This is determined at runtime and domains (x-altitude-instance for local development or the domain in prod) will be mapped by matching a value from within a tenants domains.variants array.

Applications that have a multi tenancy model, will supply an array of configs to the integration function altitudeMiddleware. It is recommended that each tenants config is in their own respective file and exported from a single file to keep the configs tidy, sectioned and consistent.

/config/index.js
import siteOne from "./siteOne";
import siteTwo from "./siteTwo";
import siteThree from "./siteThree";
export default [siteOne, siteTwo, siteThree];

Multi Tenancy Config

As well as defining tenant specific endpoints and KV keys, the config file allows application owners to extend any site specific values and expose these at runtime. One benefit that the integration allows is the ability to unlock tenant specific environment variables. Tenant specific secrets can be supplied to the build config. The integration exports an env function which can be imported and invoked inside of individual build configs. The env function takes in the reference of the environment variable as an argument not the value.

config/siteOne.js
import { env } from '@thg-altitude/astro-integration'
export default {
domains: {
default: "www.example.com",
variants: ["www.example.com"],
},
... // exsiting site setup
blog: {
secret: env('EXAMPLE_SITE_BLOG_SECRET')
}
};

The value of this environment variable can then be accessed by using the altitude global context altitude.runtime.config.

---
const { altitude, runtime } = Astro.locals
// runtime(Production) vs vite development server(Local development)
const blogSecret = runtime ? runtime.env[altitude.runtime.config.blog.secret] : import.meta.env[altitude.runtime.config.blog.secret]
---

Tenant Switching

When developing in a multi tenanted application, it may be desired that you are able to switch between tenants on the fly. For local development and DEV/UAT cloudflare environments this is easily achieved by adding a custom HTTP header to the request. The header to be used must be x-altitude-instance and the value will determine which tenant to switch to. This value should exist in that tenants config file, listed under the domains.variants array. Tenant switching will not be enabled on live domains.

Applications that are using localised domains will use the domain associated to that locale.