500 Error on Azure Web Service - deploying from GitHub deployment workflow

I have created a simple Umbraco web app that runs locally with no issues. Target framework is .NET 9 and im using Umbraco 15.3.1. I’m seriously at a loss at this point and I’ve never had this much difficulty deploying an application to azure or anywhere before, so this may be quite a bloated question but please bare with me I will format as best as I can, I really need to get to the bottom of this. This issue has been right from the start, even from out the box config and deployment - I’ve added to and changed things along the way to try and sort this so anything that seems out of place is probably that.

Note: My Azure service plan is current Basic (B1) - I’m aware the reccomended minimum is S2 but I’m not yet above the storage and memory requirements - as far as I can see.


:red_exclamation_mark:
The Problem
I’m deploying this to Azure Web Services using Sqlite, I’m trying to keep cost down/stay with free tier, so I don’t have any other storage server/database in azure at this time.

I have a github repository that is linked to my Azure Web Service for continuous deployment, this seems to work okay, except currently it seems to not generate my web.config properly from my .csproj (this may have been a recent issue off the back of attempted fixes)

It seems to be able to build and deploy okay, but fails to startup on initial deploy due to the wwwroot/media folder not being found. Which makes me think it’s crashing and can’t make this folder, maybe it’s permissions. So I created the media folder manually, and it seems to run successfully as it shows in the logs, but I still get a 500 internal server error:

Looks like there’s a problem with this site

(my custom domain) might have a temporary problem or it could have moved.

Error code: 500 Internal Server Error

The site could be temporarily unavailable or too busy. Try again in a few moments.

:hammer_and_wrench:
What I’ve Tried

I’ve tried many things, even sat with ChatGPT to try and work out this issue. (if it’s helpful I can share my chat from GPT)

  • I thought it may be the fact that Umbraco creates a nested wwwroot within Azure’s wwwroot, which may be causing issues, so tried changing the workflow to flatten this heiarchy out. No difference, reverted this.
  • Tried including the media folder - not really best practice, didnt help, reverted this.
  • Tried including the Data folder and Sqlite files as well as media and the content - didn’t help.
  • Tried to deploy the app as self contained - chat GPT told me azure doesn’t support .net 9 unless self contained, that isn’t the case for my region, reverted this.
  • Tried to create a custom web.config (seems that every time I build and deploy the .csproj settings aren’t generating the web.config on azure properly and it keeps trying to set AspNetCoreProcessPath to my apps compiled .exe, as if it was self-contained but I’ve reverted that.)

:umbraco-logo:
Code Snippetes

git actions - master.yml

# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy ASP.Net Core app to Azure Web App - myapp

on:
  push:
    branches:
      - master
  workflow_dispatch:

jobs:
  build:
    runs-on: windows-latest
    permissions:
      contents: read #This is required for actions/checkout

    steps:
      - uses: actions/checkout@v4

      - name: Set up .NET Core
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.x'

      - name: Build with dotnet
        run: dotnet build --configuration Release

      - name: dotnet publish
        run: dotnet publish -c Release --no-self-contained -o ./publish

      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v4
        with:
          name: .net-app
          path: ./publish

  deploy:
    runs-on: windows-latest
    needs: build
    environment:
      name: 'Production'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    permissions:
      id-token: write #This is required for requesting the JWT
      contents: read #This is required for actions/checkout

    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v4
        with:
          name: .net-app
      
      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.CLIENTID}}
          tenant-id: ${{ secrets.TENANTID}}
          subscription-id: ${{ secrets.SUBSCRIPTIONID}}

      - name: Deploy to Azure Web App
        id: deploy-to-webapp
        uses: azure/webapps-deploy@v3
        with:
          app-name: 'myapp'
          slot-name: 'Production'
          package: .

myapp.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <CompressionEnabled>false</CompressionEnabled> <!-- Disable compression. E.g. for umbraco backoffice files. These files should be precompressed by node and not let dotnet handle it -->
	<GenerateWebConfigFile>false</GenerateWebConfigFile>
	<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
	<AspNetCoreProcessPath>dotnet</AspNetCoreProcessPath>
	<AspNetCoreArguments>.\myapp.dll</AspNetCoreArguments>
	<AspNetCoreEnableLogging>true</AspNetCoreEnableLogging>
	<AspNetCoreLoggingOutputLocation>.\logs\stdout</AspNetCoreLoggingOutputLocation>
	<PublishTrimmed>false</PublishTrimmed>
	<PublishReadyToRun>true</PublishReadyToRun>
  </PropertyGroup>
  
  <ItemGroup>
	<Content Update="web.config" CopyToOutputDirectory="Always" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Umbraco.Cms" Version="15.3.1" />
  </ItemGroup>
  
  <ItemGroup>
    <!-- Opt-in to app-local ICU to ensure consistent globalization APIs across different platforms -->
    <PackageReference Include="Microsoft.ICU.ICU4C.Runtime" Version="72.1.0.3" />
    <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="72.1.0.3" Condition="$(RuntimeIdentifier.StartsWith('linux')) or $(RuntimeIdentifier.StartsWith('win')) or ('$(RuntimeIdentifier)' == '' and !$([MSBuild]::IsOSPlatform('osx')))" />
  </ItemGroup>


  <PropertyGroup>
    <!-- Razor files are needed for the backoffice to work correctly -->
    <CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
  </PropertyGroup>


</Project>

custom web.config file

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <!-- Set up the ASP.NET Core Module to run the app -->
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
    </handlers>

    <!-- Configure the ASP.NET Core hosting model -->
    <aspNetCore processPath="dotnet" arguments=".\myapp.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="InProcess" />
  </system.webServer>
</configuration>

appsettings.json

{
  "$schema": "appsettings-schema.json",
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information",
        "System": "Warning"
      }
    }
  },
  "Umbraco": {
    "CMS": {
      "Global": {
        "Id": "f9cef622-4531-41aa-a55a-e4c8949cfa8a",
        "UseHttps": true,
        "SanitizeTinyMce": true,
        "MainDomLock": "FileSystemMainDomLock"
      },
      "Hosting": {
        "LocalTempStorageLocation": "EnvironmentTemp"
      },
      "Examine": {
        "LuceneDirectoryFactory": "SyncedTempFileSystemDirectoryFactory"
      },
      "Content": {
        "AllowEditInvariantFromNonDefault": true,
        "ContentVersionCleanupPolicy": {
          "EnableCleanup": true
        }
      },
      "Unattended": {
        "UpgradeUnattended": true
      },
      "Runtime": {
        "Mode": "Production"
      },
      "ModelsBuilder": {
        "ModelsMode": "Nothing"
      },
      "WebRouting": {
        "UmbracoApplicationUrl": "https://myapp.com/"
      },
      "Security": {
        "AllowConcurrentLogins": false
      },
      "RuntimeMinification": {
        "UseInMemoryCache": true,
        "CacheBuster": "AppDomain"
      }
    }
  },
  "ConnectionStrings": {
    "umbracoDbDSN": "Data Source=|DataDirectory|/myapp-Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True",
    "umbracoDbDSN_ProviderName": "Microsoft.Data.Sqlite"
  }
}

Program.cs

using Microsoft.Extensions.FileProviders;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Set the DataDirectory path before setting up Umbraco
// This could be based on an environment variable or a default value
var dataDirectoryEnv = Environment.GetEnvironmentVariable("DATA_DIRECTORY");
if (!string.IsNullOrEmpty(dataDirectoryEnv))
{
    AppDomain.CurrentDomain.SetData("DataDirectory", dataDirectoryEnv);
}
else
{
    // Default to Azure persistent storage path if no env variable is provided
    var defaultDataDirectory = Path.Combine("C:\\home\\data");
    AppDomain.CurrentDomain.SetData("DataDirectory", defaultDataDirectory);
}

builder.CreateUmbracoBuilder()
    .AddBackOffice()
    .AddWebsite()
    .AddComposers()
    .Build();

WebApplication app = builder.Build();

await app.BootUmbracoAsync();

app.UseHttpsRedirection();

app.UseUmbraco()
    .WithMiddleware(u =>
    {
        u.UseBackOffice();
        u.UseWebsite();
    })
    .WithEndpoints(u =>
    {
        u.UseBackOfficeEndpoints();
        u.UseWebsiteEndpoints();
    });

app.UseStaticFiles();

await app.RunAsync();

On intial deployment, even with my settings shown above, the deployment seems to generate this web.config in azure:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <!-- Set up the ASP.NET Core Module to run the app -->
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
    </handlers>
    <!-- Configure the ASP.NET Core hosting model -->
    <aspNetCore processPath=".\myapp.exe" arguments=".\myapp.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="InProcess" />
  </system.webServer>
</configuration>

However I manually changed this in Kudu:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <!-- Set up the ASP.NET Core Module to run the app -->
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
    </handlers>
    <!-- Configure the ASP.NET Core hosting model -->
    <aspNetCore processPath="dotnet" arguments=".\myapp.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="InProcess" />
  </system.webServer>
</configuration>

It does seem to run (logs say successfully run) but still 500 errors


:white_question_mark:
Final Questions

No matter what I try, the app just does not seem to be accessible and 500’s even if its seemingly running successfully. After trying many fixes things may just be a bit out of place at this point and I may be missing something simple, but it’s been days of wrestling with this and I can’t figure it out.

  • Do I need to use some other storage resources in azure?
  • Do I need to change permissions? (if so, how? in kudu cmd?)
  • Have I missed a step?
  • Do I need to upgrade the service plan to S2?
  • Please help? :sob:

If any more info is needed, please ask.