I have a multi-tenant application where each customer gets their own instance. Some of the customers have purchased additional functionality, such as custom branding, for their copy of the application. How can I have a single deployment process but still deploy custom features to some customers such as custom branding?
One of the downsides to an al a carte pricing model. Easy to sell, harder to deploy. For this solution, we will be leveraging the following functionality in Octopus Deploy.
You can also take a look at more multi-tenant examples we have on our samples instance.
Background
The underlying goal of Octopus Deploy’s multi-tenant functionality is to provide you a single deployment process for all of your tenants. A tenant could be customers or users or AWS regions or data centers. The deployment process is the same, except for differences in variables such as connection strings or customer name.
In some cases, the deployment process has to be different per customer. You need to enable or disable steps based on some sort of requirements. This is where tenant tags can be used. You can create 1 to N tenant tags sets for your tenants to leverage. The tenant tag sets can cover a wide range of differences.
You assign the tenant tag(s) to a tenant on the tenant overview screen.
Packaging up your custom branding changes
Branding typically means .css files and images. They are unique per customer. You can take a few approaches with packaging up those resources.
- Each customer’s resources are stored in a unique package. The package only contains those custom resources, nothing else. The deployment extracts to a custom resource directory that the web application looks for.
- All customer resources are in the same package. The deployment sets a config entry indicating which folder/resources to use.
This walkthrough will do its best to account for both options.
Create and Assign Tenant Tag
First, we need the tenant tag defined. If we go back to the tenant tag sets screen in the library we will see that tenant tag.
Please Note: The tenant tag sets will appear only after you add your first tenant.
Now that we have the tenant tag, we can go back to the tenant and assign it to them, as we did for this tenant
.
Custom Package Option
If each customer has their own package containing the branding resources you should do this step. First, create a variable set for your application or project. In my example, I have a variable set for my project, OctoPetShop-Web. Once the variable set is defined, we will want to create a variable template. See this page for an example.
Make note of the default value. This allows a “default” package to be used in the event a customer doesn’t have custom branding.
Next, make sure the variable set is tied to the project like below.
After that variable set is tied to the project, any tenant associated with the project will be able to override that variable’s default value. That is done by going to tenant -> variables -> common variables. In this tenant’s case, we have a custom package defined for them.
Now we will need to deploy that package. In this case, I have a step in my deployment process which deploys the package.
The step has the run condition set to run only when it is deploying to a tenant with that tenant tag.
The downside to this option has to deal with how Octopus snapshots a release. When you create a release you have to specify a package version. However, the package is going to be different per tenant.
What this means is you will have to have the same package version number (for example 1.1.2
) for all tenants. If there are minor changes to the branding, say you got an updated image from the customer, then you’ll have to either update the version number for all customers (1.1.3
) or overwrite the existing package in Octopus Deploy when you push it specifying the –overwrite-mode opion.
All custom branding stored in the same package option
In this option, all the custom branding resources (css, images, etc) are stored in the same package as the website. Each customer has a different folder for their custom branding.
- Root folder
- Custom Branding
- Customer A
- Customer B
- Customer C
- Custom Branding
In this example, you would have a configuration entry indicating the folder to pull resources from. One cool thing about tenant tags is they can be used to scope variables. The folder is going to be the same across all environments, so we will want to use the common variables setting for tenants.
To do this, create a variable set for either the project or the application. In my example, I created a variable set for the project. I added a new variable template for the variable set.
Next, in the variables section for the variable set, I created a new variable OctoPetShop.Web.CustomBranding.Folder
. For tenants with the Branding
tenant tag I want to use the variable template OctoPetShop.Web.CustomBranding.Tenant.Folder
, but for everyone else, they get the default folder.
You can set custom scope by clicking on the Define Scope
box and clicking the open editor
button.
If you see this window, then click on the Define Scope
button.
Doing that will show you the tenant tags you can scope a variable to.
Next, make sure the variable set is tied to the project like below.
After that variable set is tied to the project, any tenant associated with the project will be able to override that variable’s default value. That is done by going to tenant -> variables -> common variables. In this tenant’s case, we set the value to AllPets
.
In the project, you can leverage configuration transforms functionality to use that new variable. If I wanted to replace the CustomBranding setting in the AppSettings object for a .NET core app I would set the value to AppSettings:CustomBranding
.
Please note: If you are using .NET core, please be sure to turn on JSON Configuration Transforms.
Wrapping Up
This walkthrough covered a few options on how to have custom branding for a multi-tenant application. Each approach has its pros and cons. You’ll need to do your due diligence to determine which option works best for your requirements.
This can also be used for other things besides custom branding. This pattern can be reused for any number of custom features you support.