Publishing to Azure
There are five methods for publishing a Lagoon application to Azure App Service:
- Azure App Service Windows.
- Azure App Service Linux.
- Azure App Service with Container, Linux or Windows.
- Azure Container Service, Linux only.
- A virtual machine, with IIS or Apache publishing.
With the Azure App Service solutions, you can run scheduled tasks, or WebJobs, in the App Service context.
In the following publishing examples, az CLI is used to deploy the infrastructure resources. In a production context, it is recommended to use Bicep scripts to manage Infrastructure As Code.
Publishing to Azure App Service Windows
Preparing the deployment package
- Publish specifying
--runtime win-x64. - Create a ZIP file containing the publication.
SET APP_VERSION=1.0.0
SET APP_NAME=MYAPP
SET PUBLISH_PATH=C:\Temp\(Publish)\%APP_NAME%
SET PUBLISH_TEMP_PATH=%PUBLISH_PATH%\ZIP
SET PUBLISH_ZIP_FILE=%PUBLISH_PATH%\%APP_NAME%-v%APP_VERSION%.zip
REM To fix the error NETSDK1112: The runtime pack for Microsoft.NETCore.App.Runtime.Mono.browser-wasm was not downloaded. Try running a NuGet restore with the RuntimeIdentifier 'browser-wasm'
dotnet restore Client\%APP_NAME%.Client.csproj --runtime browser-wasm
REM Build and publish to a temp folder
dotnet publish Server\%APP_NAME%.Server.csproj --configuration Release -p:Version=%APP_VERSION% --runtime win-x64 --no-self-contained --output "%PUBLISH_TEMP_PATH%"
REM Create a ZIP package for deployment
"%ProgramFiles%\7-Zip\7z.exe" a "%PUBLISH_ZIP_FILE%" "%PUBLISH_TEMP_PATH%\.\*"
Deployment
- Specify
--https-only truewhen creating the App Service to redirect HTTP requests to HTTPS. - Set
--always-on truefor the App Service so that it does not pause on inactivity, a prerequisite for WebJobs. - Declare the
WEBSITE_TIME_ZONEvariable asRomance Standard Timeso the Web App Service uses the Brussels, Copenhagen, Madrid, Paris time zone.
SET RESOURCEGROUP=MYAPP-TEST
SET LOCATION=northeurope
SET SITENAME=myapp-test-as
SET PLANNAME=%SITENAME%p
SET PLANSKU=B1
REM login supports device login, username/password, and service principals
call az login
REM create a resource group for your application
Echo Resource group...
call az group create --name %RESOURCEGROUP% --location %LOCATION%
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM create an appservice plan (a machine) where your site will run
Echo App plan...
call az appservice plan create --name %PLANNAME% --location %LOCATION% --sku %PLANSKU% --resource-group %RESOURCEGROUP%
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM create the web application on the plan
Echo App service...
call az webapp create --name %SITENAME% --plan %PLANNAME% --resource-group %RESOURCEGROUP% --https-only true
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM configure the app service
Echo App service configuration...
call az webapp config set --name %SITENAME% --resource-group %RESOURCEGROUP% --always-on true
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM configure environment variable for the app service
Echo Setting environment variables...
call az webapp config appsettings set --name %SITENAME% --resource-group %RESOURCEGROUP% --settings WEBSITE_TIME_ZONE="Romance Standard Time"
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM Deploy zip
Echo Deploy app...
call az webapp deploy --resource-group %RESOURCEGROUP% --name %SITENAME% --type zip --src-path "%PUBLISH_ZIP_FILE%"
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM Browse to the site
Echo Open website...
call az webapp browse --name %SITENAME% --resource-group %RESOURCEGROUP%
Scheduled tasks, optional
- Create a
WebJobsfolder at the root of theMyApp.Batchproject. - Edit the
MyApp.Batch.csprojfile to add the following lines:
<ItemGroup>
<None Include="WebJobs\**\*">
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
<TargetPath>app_data\jobs\triggered\%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
</None>
</ItemGroup>
- Create a
DailyTasksubfolder inside theWebJobsfolder. TheDailyTaskname can be renamed: it is the name of the WebJob. - Create the
run.cmdandsettings.jobfiles inside theDailyTaskfolder.
run.cmd
@echo off
rem -- Set current directory to "C:\home\site\wwwroot" to enable loading of "appsettings*.json".
cd /D %WEBROOT_PATH%
call myapp.exe batch --send-reminder
Adapt the command as needed.
settings.job
{
// WEBSITE_TIME_ZONE environment variable must be set to 'Romance Standard Time' for Bruxelles, Copenhague, Madrid, Paris
// Runs at 4:30 AM, Monday to Friday
"schedule": "0 30 4 * * 1-5"
}
Remember to properly set the WEBSITE_TIME_ZONE environment variable when creating the App Service.
Publishing to Azure App Service Linux
Preparing the deployment package
- Publish specifying
--runtime linux-x64 --no-self-contained. - Create a ZIP file containing the publication.
SET APP_VERSION=1.0.0
SET APP_NAME=MYAPP
SET PUBLISH_PATH=C:\Temp\(Publish)\%APP_NAME%
SET PUBLISH_TEMP_PATH=%PUBLISH_PATH%\ZIP
SET PUBLISH_ZIP_FILE=%PUBLISH_PATH%\%APP_NAME%-v%APP_VERSION%.zip
REM To fix the error NETSDK1112: The runtime pack for Microsoft.NETCore.App.Runtime.Mono.browser-wasm was not downloaded. Try running a NuGet restore with the RuntimeIdentifier 'browser-wasm'
dotnet restore Client\%APP_NAME%.Client.csproj --runtime browser-wasm
REM Build and publish to a temp folder
dotnet publish Server\%APP_NAME%.Server.csproj --configuration Release -p:Version=%APP_VERSION% --runtime linux-x64 --no-self-contained --output "%PUBLISH_TEMP_PATH%"
REM Create a ZIP package for deployment
"%ProgramFiles%\7-Zip\7z.exe" a "%PUBLISH_ZIP_FILE%" "%PUBLISH_TEMP_PATH%\.\*"
Deployment
- Create a Linux App Service Plan with
--is-linux. - Specify
--https-only truewhen creating the App Service to redirect HTTP requests to HTTPS. - Specify the
DOTNETCORE:8.0runtime when configuring the App Service. - Set
--always-on truefor the App Service so that it does not pause on inactivity, a prerequisite for WebJobs. - Declare the
TZvariable asEurope/Parisso the Web App Service uses the Paris time zone. - Declare the
WEBSITE_SKIP_RUNNING_KUDUAGENTvariable asfalseto allow the use of WebJobs.
SET RESOURCEGROUP=MYAPP-TEST
SET LOCATION=northeurope
SET SITENAME=myapp-test-as
SET PLANNAME=%SITENAME%p
SET PLANSKU=B1
REM login supports device login, username/password, and service principals
call az login
REM create a resource group for your application
Echo Resource group...
call az group create --name %RESOURCEGROUP% --location %LOCATION%
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM create an appservice plan (a machine) where your site will run
Echo App plan...
call az appservice plan create --name %PLANNAME% --location %LOCATION% --sku %PLANSKU% --resource-group %RESOURCEGROUP% --is-linux
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM create the web application on the plan
Echo App service...
call az webapp create --name %SITENAME% --plan %PLANNAME% --resource-group %RESOURCEGROUP% --https-only true --runtime "DOTNETCORE:8.0"
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM configure the app service
Echo App service configuration...
call az webapp config set --name %SITENAME% --resource-group %RESOURCEGROUP% --always-on true --startup-file="dotnet %SOLUTION_NAME%.Server.dll"
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM configure environment variable for the app service
Echo Setting environment variables...
call az webapp config appsettings set --name %SITENAME% --resource-group %RESOURCEGROUP% --settings TZ="Europe/Paris" WEBSITE_SKIP_RUNNING_KUDUAGENT="false"
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM Deploy zip
Echo Deploy app...
call az webapp deploy --resource-group %RESOURCEGROUP% --name %SITENAME% --type zip --src-path "%PUBLISH_ZIP_FILE%"
IF NOT %ERRORLEVEL%==0 GOTO ERROR
REM Browse to the site
Echo Open website...
call az webapp browse --name %SITENAME% --resource-group %RESOURCEGROUP%
Scheduled tasks, optional
- Create a
WebJobsfolder at the root of theMyApp.Batchproject. - Edit the
MyApp.Batch.csprojfile to add the following lines:
<ItemGroup>
<None Include="WebJobs\**\*">
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
<TargetPath>app_data\jobs\triggered\%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
</None>
</ItemGroup>
app_data\jobs\triggered and the files in the folder must be lowercase on Linux.
- Create a
DailyTasksubfolder inside theWebJobsfolder. TheDailyTaskname can be renamed: it is the name of the WebJob. - Create the
run.shandsettings.jobfiles inside theDailyTaskfolder.
run.sh
#!/bin/bash
# WEBSITE_SKIP_RUNNING_KUDUAGENT environment variable must be set to false in order for webjobs to function on linux apps.
# Set current directory to "/home/site/wwwroot" to enable loading of "appsettings*.json".
cd $WEBROOT_PATH
myapp batch --send-reminder
Adapt the command as needed.
settings.job
{
// TZ environment variable must be set to 'Europe/Paris' for Paris
// Runs at 4:30 AM, Monday to Friday
"schedule": "0 30 4 * * 1-5"
}
Remember to properly set the WEBSITE_SKIP_RUNNING_KUDUAGENT and TZ environment variables when creating the App Service.
Scheduled Linux tasks with a read-only file system
If WEBSITE_RUN_FROM_PACKAGE=1, the file system becomes read-only and the batch script must be written to a different folder: /home/site/jobs/triggered.
The solution consists of creating the files in the correct location at application startup.
appsettings.Development.json
{
"WebJob": {
"Folder": "C:\\Temp\\MyApp\\WebJob",
"CronExpression": "0 0 9 * * 1-5" // Runs at 8:00 AM, Monday through Friday
}
}
appsettings.Production.json
{
"WebJob": {
"Folder": "/home/site/jobs/triggered/Batch",
"CronExpression": "0 0 8 * * 1-5" // Runs at 8:00 AM, Monday through Friday
}
}
Server\Main.cs
///<inheritdoc/>
protected override Task OnStartAsync(IServiceProvider scopedServiceProvider, CancellationToken cancellationToken)
{
InitializeWebJob();
return Task.CompletedTask;
}
/// <summary>
/// Create the WebJob from the app_settings.
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void InitializeWebJob()
{
string folder = Configuration["WebJob:Folder"];
if (string.IsNullOrEmpty(folder))
{
throw new NotImplementedException("WebJob folder is not configured in appsettings.json");
}
Directory.CreateDirectory(folder);
// run.sh script is copied from the WebJob folder in the WebJob directory
string runSourcePath = Path.Join(Path.GetDirectoryName(typeof(Main).Assembly.Location), "WebJob", "run.sh");
string runPath = Path.Join(folder, "run.sh");
if (!File.Exists(runPath) || File.GetLastWriteTime(runSourcePath) > File.GetLastWriteTime(runPath))
{
File.Copy(runSourcePath, runPath, true);
}
// settings.job file is created in the WebJob directory
string settingsPath = Path.Join(folder, "settings.job");
string cronExpression = Configuration["WebJob:CronExpression"];
if (string.IsNullOrEmpty(cronExpression))
{
if (File.Exists(settingsPath))
{
File.Delete(settingsPath);
}
}
else
{
string content = $"{{\"schedule\": \"{cronExpression}\"}}";
if (!File.Exists(settingsPath) || File.ReadAllText(settingsPath) != content)
{
File.WriteAllText(settingsPath, content);
}
}
}
run.sh
#!/bin/bash
# Set current directory to "/home/site/wwwroot" to enable loading of "appsettings*.json".
cd $WEBROOT_PATH
myapp batch --mySampleArgument
The +x execute flag must be set on the file.
# Preparing linux build
- bash: |
dotnet tool update --global LGN --version $(lgnVersion)
lgn fix-charset
ls -al "Server/WebJob"
chmod +x "Server/WebJob/run.sh"
ls -al "Server/WebJob"
displayName: Preparing linux build
MyApp.csproj
<None Update="WebJob\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>