Branding, customer-specific customizations, and deployment via git & Docker

I’m faced with the following situation, which is not necessarily Django specific:

I have a Django application that is currently being deployed via Dokku. It could also be deployed using plain Docker in the future, but the point is that it is deployed in an immutable environment that exploits git.

I find myself in a situation in which multiple different customers want to use the application, and make a few basic customizations. There fall into three categories:

  1. graphical assets: a customer may want to display their logo or change some of the graphical elements of the application
  2. text: my application uses i18n. Some of the strings displayed in the UI reference the company name and therefore must be customized on a per-customer basis, possibly in multiple languages. My app also sends emails for which the templates may need to be customized, although that mainly falls under text & graphical assets changes
  3. features: some features may need to be switched on/off for certain customers

The multi-tenancy of my application will be handled by going the multi-instance approach: one running instance of the application per customer, each with its customizations.

Keeping in mind that I’m the one who controls the deployment (i.e. the software will be distributed as a SaaS), the third category of customizations seems to be easily solved by using env vars and reading those variables in specific places of the application code to turn on/off features.

The biggest problem comes from the first two categories. I can imagine there could be a config file (e.g. JSON) which has a key for each of those customizable assets, but the problem is that those files will also need to be included inside of the codebase for that to work. For example, if I want to load a specific logo, I can specify the resource URI in the config file, but I will also need to have that file inside of the repository for the app. Likewise, if I want to enable certain UI strings / email templates to be customized for my clients, I may enable a directory which contains translations files to be merged with the customer-agnostic i18n files, but I would somehow need to include those files in the build.

Of course, I can’t just add them to the git repository of the project, as that is unique and shared among all clients.

How would you tackle this issue?

1 Like

We tackle this issue by storing customization-related information in the database, and build a set of fixtures for each client that gets loaded at installation time.

1 Like

What kind of information are you actually storing in the db? Is it stuff like URI’s, or are you storing the actual asset files in the db (e.g. Binary encoded)?

How are you retrieving the fixtures for the client when deploying? Do you have like a separate branch for each customer, or are you fetching them from elsewhere?

The assets are stored in the database. Some of the larger images are stored in binary, some of the smaller are stored base64 to be included directly in the img tag.

Since we perform the deployment, we keep these resources within a “fixtures” directory in the repo, with a subdirectory per client. (There’s also a base directory for the default values. That gets loaded first, then the custom fixtures get loaded for the client, overwriting any of the defaults being replaced.)

Note that we (can) replace a lot more than just logos. We also have the system designed to allow for the customization of things like role and permission names, menu structure, etc.

This is interesting. Do you have something like an Assset model? If so, I assume you’ll have a (circa) fixed number of instances of that model per application instance. Is there anything special you’re doing to ensure those instances exist throughout the life of the application? (E.g. They they aren’t deleted by mistake)

If my intuition of the Asset model is correct, I’m assuming these are fixtures used to load and populate that model. Is that the case?

Do you gave any code example that shows how you’re implementing this approach in practice? I would just be curious to see what a working, production ready approach look like.

Truth be told, I opened this thread to get some insights as to how this issue could be tackled in Django only, but my real use case is that I have a DRF backend & a Vue frontend. I’m exploring some solutions that enable dealing with this on the frontend only, but in the meantime I’m interested in seeing if anything better can be done exploiting Django too.

Not specifically as a “special” or “different” model.

We have a model that contains files of all sorts. Some of those files are images, some are PDFs, etc. Other resources (views, templates, etc) have references to those files. Most of the files can be changed by the customer - there are forms/views for that. However, the customer has no facility to delete a resource. (The customer does not get access to the admin.)

Should any resource get deleted by mistake (there are 4 of us with admin accounts), we either retrieve it from the backup or reload it from the fixture.

Models - plural. These assets aren’t isolated to a single table - they’re replacements for our defaults to our system.
For example, we have a Role table. That model has a display_name field. There is a customer who wanted to change the name of one of the roles. We created a fixture to update that field of that model and added it to the customer’s fixture directory.

There’s not really a lot of code to show. We create fixture files using dumpdata and load them using loaddata during the install.

1 Like

Gotcha. One thing I failed to mention about your previous response:

If I understand this correctly, you’re not delivering a multi-tenant application, in which you have different customers share the same application instance, right?

Therefore, this means that customers will also get other customers’ assets in their build. How do you and your team feel about this?

From a separation of concerns standpoint, I would somehow want more separation between the part that’s shared about customers (the code) and the one that’s not (the assets). I really like the idea of employing models to handle these customizations, but I’m not too sure about putting everyone’s files in the same repo.

Someone suggested having a branch for each customer but I really dislike that idea, as I believe things would get messy when branching for other reasons (e.g. feature branches).

Another suggestion I got is to have a separate repository for each client: each repo contains the assets, any customer-specific config files, and a dockerfile which does a git clone from the repo containing the codebase and then merges the assets contained in the per-customer repo. I am kinda torn in between different approaches that I think could work.

The customers never get their hands on anything. We have 100% control over the installation. Anything the customer doesn’t get is deleted.

Agreed. We looked at that approach and decided it wasn’t worth the hassles.

Thank you for this discussion! I’m currently in a similar situation deciding how best to manage our Django SaaS with individual deployments per customer. We have not launched yet and I’m working on a plan to be able to onboard our first few customers.

Thanks! Is this so that you can perform re-installs for your clients?

I imagine that if your customer asked you to change the name of the role again, you could simply login to their admin and edit that value in the database.

Do you instead, modify the source code for the fixture, commit it to version control, deploy the updated code, and then reload the fixture?

That’s an interesting idea that I hadn’t heard of yet. It seems like it might allow for a lot of flexibility but I’ve not heard anyone doing it, which makes me hesitate that maybe it’s not a popular approach?

Yes to the second. We do this to reinstall, replicate, reuse, document, take your pick. This sort of “documented and reproduceable” process is generally required for the type of work we do for our contracts. Pretty much everything we do must be auditable. That, and these processes must be able to outlast the employment of any individual.

1 Like