Resource Content (read and write) on Azure Slotted Deploys

Howdy team,

I am seeking some advice on Umbraco Azure Slotted deploys.

We have recently assumed the development and management of a basic Umbraco 13 site. We have opted for a simple architecture:

  • Test Instance (Azure Application Instance, Azure Blob Storage, and Azure SQL Database)
  • Staging / Production Instance (Slotted Azure Application Instances, Azure Blob Storage, and Azure SQL Database)

We run and deploy on:

  • Umbraco 13
  • Azure Linux Application Instances
  • Via GitHub CICD pipelines - slot swap automated to happen once the Staging Slot returns `healthy`

We are intermittently running into some issues when deploying to the staging slot - pre-slot swap to production once the Staging slot is warm.

The issue manifests itself with the Application not being able to read (and presumably write) to ~/site/wwwroot/umbraco/Data/tmp:

  • I highly suspect we are running into some resource contention issues between the Staging and Production slots, however I am unsure how to test this theory, or resolve any issues based on the Umbraco documentation.
  • Assumably this is related to MainDomLock(currently set to FileSystemMainDomLock) and who is ‘Main’ between Staging and Production at any given time.
  • I am waiting for the issue to pop-up again to see if it was Staging or Production that falls over (I was rushing to fix it last time without actually taking notes sorry)

What is the recommended approach in such a situation:

  • I have read the docs on MainDomLock: SqlMainDomLock and MainDomKeyDiscriminator (indicated at in Global Settings | CMS | Umbraco Documentation ) likely this is something we need to implement, however I would love a little more material on how xD?
  • Am I barking up the wrong tree?

Any assistance would be much appreciated :slight_smile:

Hi @leuan-edmonds_ecwell

Welcome to the forum!

The first thing to check is that you’ve got the LocalTempStorageLocation set as per the guidance on this page:

There are specific settings you need to follow to run Umbraco on Azure.

For slot swaps specifically, switch to SqlMainDomLock and give each slot its own discriminator. In appsettings.json on both slots:

"Umbraco": {
  "CMS": {
    "Global": {
      "MainDomLock": "SqlMainDomLock"
    }
  }
}

Then in Azure, set the discriminator as an app setting and tick the Deployment slot setting box so it does not move with the code on swap:

Production slot:

Umbraco__CMS__Global__MainDomKeyDiscriminator = MySiteProduction

Staging slot:

Umbraco__CMS__Global__MainDomKeyDiscriminator = MySiteStaging

Justin

If you were running on Windows, the recommended Azure settings without the discriminator should be fine, as each slot uses its own temp folder, but on Linux %TEMP% which EnvironmentTemp uses does not exist (according to AI).

The only downside to using SqlMainDomLock is that it relies on the database not the local file system.

Yes this was something that confused me, with "LocalTempStorageLocation": "EnvironmentTemp" set, I was still seeing Umbraco attempting to create files in ~/site/wwwroot/umbraco/DATA/tmp, which either meant:

  1. I’m doing something wrong; or
  2. It just doesn’t work in Linux.

I could obviously set it to /tmp, however the docs indicate that using EnvironmentTemp is the recommended way - I assumed EnvironmentTemp would point at /tmp on Linux…

Thanks Justin, i’ll give it a go today and let you know how we get on.

Regards

1 Like

Finally getting around to trailing this and got hit with a realization.

My understanding is that `MainDomLock` is the mechanism that ensures only one application instance (“Main Domain”) performs critical operations across your environment - preventing conflicts with Lucene indexes, writing cache files, etc.

But the value of MainDomKeyDiscriminatoris arbitrary I suppose; it has no precedence. It sounds like the determining factor is ‘who has the lock right now’ and when that instance shuts down, it yields to the other (or more the other takes over) - presumably on a slotted deploy.

This seems a little happy path / wishful thinking - what if I naturally just want to restart my production instance (and I don’t want to yield ManDom to the Staging Slot) - i’d likely have to stop my Staging slot too.

I suppose in a perfect world, if I never shutdown / restart my production instance, it would ALWAYS stay as MainDom (even through the slot swaps - thanks to MainDomKeyDiscriminator) - ergo happy path xD

Just food for thought I suppose - or maybe I am fundamentally misunderstanding something haha

Hi @leuan-edmonds_ecwell

Here is the AI summary for MainDomLock:

MainDomLock is a configuration setting in Umbraco that determines how the application acquires the “MainDom” status, which controls which server instance in a load-balanced or scaled environment is responsible for database initialization, cache rebuilding, and scheduled tasks.

For Azure Web Apps, the recommended configuration is FileSystemMainDomLock, which uses lock files in the local temporary storage to manage this status, avoiding file locking issues associated with the remote file share used by Azure.

  • Configuration: In appsettings.json, set “MainDomLock”: “FileSystemMainDomLock” under Umbraco.CMS.Global.
  • Azure Environment Variables: When configuring via Azure App Service settings, use the key UMBRACO__CMS__Global__MainDomLock.
  • Alternative: SqlMainDomLock was historically used but can cause issues with read-only database replicas or Azure’s file system structure; it is generally superseded by the file system approach for standard Azure deployments.
  • Slot Swaps: In Azure deployment slots, ensure each slot has a unique MainDomKeyDiscriminator if using SQL-based locking, though FileSystemMainDomLock handles slot isolation more naturally by relying on local temp storage.

I hope that helps.

Justin

That is the annoying thing with AI, when I asked I got the opposite answer xD

Question:

If I have an Azure slotted Production and Staging setup for Umbraco, Which MainDomLock mechanism should I be using

Answer:

For an Azure App Service setup using Production + Staging deployment slots with Umbraco CMS, you should generally use:

"Umbraco": {
  "CMS": {
    "Global": {
      "MainDomLock": "SqlMainDomLock"
    }
  }
}

…and give each slot its own unique MainDomKeyDiscriminator.

Example:

Production slot

Umbraco__CMS__Global__MainDomKeyDiscriminator = MySiteProduction

Staging slot

Umbraco__CMS__Global__MainDomKeyDiscriminator = MySiteStaging

Mark those settings as Deployment Slot Settings in Azure so they stay with the slot during swaps.

Why:

  • FileSystemMainDomLock is the standard recommendation for Azure Web Apps generally.

  • But with slot swaps, both slots may temporarily compete for MainDom ownership against the same database/content state.

  • SqlMainDomLock plus a slot-specific discriminator avoids conflicts and startup issues during warmup/swap operations.

And the docs are not realllllly helpful, oh well, I might just trail SqlMainDomLock for a while and see if we see better stability.

Hi @leuan-edmonds_ecwell ,

I was checking some resources on this and found few which I think might help. Also taken some help from AI by feeding the earlier responses, here’s what it gave:

Resource 1: The Official Stance on MainDomKeyDiscriminator and Slot Swaps

The most critical piece of evidence regarding why slots fight over resources and how to fix it is buried in the Global Settings Reference documentation, not the main Azure guide.

What the documentation says:

“Deployment slots for a given Azure App Service share the same machine name. Without additional configuration, they will share a MainDomKey and therefore compete for MainDom status. This can be undesirable if attempting to deploy to a deployment slot followed by a swap with the production slot as once traffic has switched to the new instance the old production instance reboots and can re-acquire MainDom status… To prevent this from occurring you can specify a MainDomKeyDiscriminator which should be set as a slot-specific configuration to prevent the slots from competing for MainDom status.”

Why this matters to the OP: This officially validates the member’s suspicion. Because Azure slots share a machine name and share the underlying wwwroot file system, they are actively fighting for control. By implementing SqlMainDomLock and assigning a unique sticky MainDomKeyDiscriminator to each slot (e.g., Prod and Staging), Umbraco treats them as two completely isolated applications that happen to point to the same database, eliminating the conflict.

Resource 2: The SqlMainDomLock vs FileSystemMainDomLock Debate

The member received conflicting advice about which lock to use because the “best practice” depends entirely on whether they are using slots.

What the documentation says: The main Azure guide recommends "MainDomLock" : "FileSystemMainDomLock".

The Technical Reality (The “Why”): FileSystemMainDomLock works perfectly on a single Azure App Service because it relies on placing a physical file lock in the directory. However, when you introduce Deployment Slots, both slots read from the same remote file share (wwwroot). If you use FileSystemMainDomLock with slots, both Staging and Production try to lock the exact same physical file.

This is why the fallback to SqlMainDomLock is required for slotted architectures. It moves the lock mechanism away from the shared file system and into the database, where the MainDomKeyDiscriminator can safely keep the two instances separated.

Resource 3: The Linux Temporary File System Reality

The member noted that setting "LocalTempStorageLocation": "EnvironmentTemp" was still generating files in ~/site/wwwroot/umbraco/Data/tmp, leading to read/write errors.

What the documentation says:

“In an Azure Web App, we want these [temp files] to be created in the local storage of the actual server that Azure happens to be used for the Web App… This is required for both the performance of the website as well as to prevent file locks from occurring due to the nature of Azure Web Apps shared files system.”

The Technical Reality (Linux vs. Windows): On Azure Windows App Services, the EnvironmentTemp setting resolves cleanly to the %TEMP% directory, which is isolated per worker instance.

On Azure Linux App Services, the environment variables don’t always map the same way, and Umbraco often falls back to creating a Data/tmp folder inside the shared wwwroot directory. Because the wwwroot is a network share attached to the Linux container (not local memory), it is notoriously slow and subject to locking contention between the slots.

To achieve the isolated local storage the documentation demands on a Linux container, the member must explicitly point Umbraco to the Linux root temp directory:

"Hosting": {
  "LocalTempStorageLocation": "Custom",
  "LocalTempStorageDefaultPath": "/tmp/umbraco-temp"
}

Hope it helps!

Hi @leuan-edmonds_ecwell

If you’ve followed the docs and tried different variations of the MainDom settings without any luck, it may be worth you raising it on the Umbraco issue tracker to see if anyone from HQ can help. It could be that there are limitations or issues specifically around slot swaps on Linux, especially if you can consistently recreate the issue.

Justin

2 Likes

Sadly Umbraco doesn’t support this, the docs define you can only pick either EnvironmentTemp or Default, custom paths cannot be set.

You could sym-link a directory under the Default path and point this at /tmp, however I am not sure we are quite there just yet.

I’ll do some playing, and see if we reach a more stable state :slight_smile:

1 Like

you could maybe replace the IHostingEnvironment concrete instance with your own?

Umbraco-CMS/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs at main ¡ umbraco/Umbraco-CMS

Also is the main issue here that you are sharing a db between production and staging? That to me seems fraught with danger? (as staging could be used to change production content?)

As you have a shared DB perhaps you should take the view that this then becomes a load balanced set up, which it is?
So explicitly set the serverRole to SchedulingPublisher for Production, and Staging becomes a Subscriber
(assuming staging doesn’t need backoffice access) That might mitigate the MainDom shortcomings?

Hi @mistyn8

I think he is just referring to the staging slot for slot swaps, that would point to the same database as it’s just a warmed up version of live ready to swap out. I would like to think a full proper staging environment is separate with it’s own app services and database (but @leuan-edmonds_ecwell can confirm?).

You’d still have the same though.. if this was warming up against the same DB.. you’ve made a load balanced environment.. server roles could be set looking at the environment name. Which would be slot sticky..

Also you’d have to be very careful warming the staging slot up against the same DB, as schema changes etc would be applied to the DB, potentially bringing down the production site?

Another one to mention is WEBSITE_DISABLE_OVERLAPPED_RECYCLING=1
MainDom Lock Timeout on Azure App Service - Looking for Solution - Umbraco community forum
So that would stop two sites (main and slot) from running a concurrently, but then you’ve potentially lost your reason for hot slot swapping in the first place?

Also I’m questioning that a slot would share a filesystem, I thought slots were completely independent, so the FileSystemMainDomLock should be fine in a slot scenario? :thinking:

So wondering if the crash is actaully the fact in v13 you can’t have two SchedulingPublishers (backoffices) which explict server roles could solve,

public class ServerRegistrar : IServerRoleAccessor
{
    private readonly IWebHostEnvironment _env;

    public ServerRegistrar(IWebHostEnvironment env)
    {
        _env = env;
    }

    public ServerRole CurrentServerRole
    {
        get
        {
            return _env.EnvironmentName.ToLower() switch
            {
                string n when n.EndsWith("api") => ServerRole.SchedulingPublisher,
                "development" => ServerRole.Single,
                _ => ServerRole.Subscriber,
            };
        }
    }
}

public class RegisterServerRegistrar : IComposer
{
    public void Compose(IUmbracoBuilder builder) => builder.SetServerRegistrar<ServerRegistrar>();
}

ps you can now load balance the backoffice in v17 (with config changes) :slight_smile:

Hi @mistyn8

My understanding is they should get the correct server role on swap, so the production slot becomes the SchedulingPublisher and the staging slot would be Subscriber. You are correct that in v13 you can only have one SchedulingPublisher.

They will have their own file systems, but on Linux the FileSystemMainDomLock does not appear resolve to EnvironmentTemp, so it falls back to the local ~/site/wwwroot/umbraco/Data/tmp which is on a network mount and not designed for high-frequency temp file work so is most likely holding locks.

Justin

No worries.. just no mention of serverRoles in the original posting.. or that this is potentially a loadbalanced setup as multiple slots involved (with a shared DB)..

The post I linked to also mentioned slot swaps holding on to tmp locks..
even though the ~/site/wwwroot/umbraco/Data/tmp should be a different fileshare bewtween slots..
with WEBSITE_DISABLE_OVERLAPPED_RECYCLING=1 being the solution :slight_smile:

Hopefully @leuan-edmonds_ecwell gets to a solution that works :crossed_fingers:

ps as you also mentioned.

if this isn’t linux friendly.. then issue to be raised?

switch (_hostingSettings.CurrentValue.LocalTempStorageLocation)
            {
                case LocalTempStorage.EnvironmentTemp:

                    // environment temp is unique, we need a folder per site

                    // use a hash
                    // combine site name and application id
                    // site name is a Guid on Cloud
                    // application id is eg /LM/W3SVC/123456/ROOT
                    // the combination is unique on one server
                    // and, if a site moves from worker A to B and then back to A...
                    // hopefully it gets a new Guid or new application id?
                    var hashString = SiteName + "::" + ApplicationId;
                    var hash = hashString.GenerateHash();
                    var siteTemp = Path.Combine(Path.GetTempPath(), "UmbracoData", hash);

                    return field = siteTemp;
1 Like