So you're done building your .NET MAUI application, congrats! Now it's time to publish them, This can be a tricky topic to navigate, even to dedicated DevOps engineers who do this regularly because of the number of moving pieces in the puzzle.
To help you, I have combined several resources that are a great place to get started. These are not only a good Getting Started experience, but it's also a good thing to bookmark as a quick look-up reference.
First, let's go through some links to the Microsoft docs. The MAUI team has put a lot of effort in making these articles helpful and relevant packaging and publishing your application. These docs are born out of the efforts of many repo issue conversations and internal experiences on the team. they saw the difficulties devs were having and refined the documentation to tailor to those topics.
There are many more article in this section, but I wanted to combine the ones you want to definitely visit.
Understanding project Confguration and various flags
- Android
- iOS
- Windows
- MacCatalyst
Although I cannot share exact details due to the sensitive nature of code signing certificates and publisher identities, but we can still review the general setup where you can plug in your details.
The microsoft docs above explain what most of this is, and how you can create the prerequisites, the demos are intended to show you how you can set it up in your CI-CD workflows
- MAUI Project Archive/Publish for VSCode - This tool is an extension for Visual Studio Code and provides a set of essential tools to streamline the process of packaging and publishing a .NET MAUI app.
- DevOps Examples - This repo has many different projects types and is intended ot show how to write workflows to compile each. One is a .NET MAUI project and the accompanying workflow has a separate job for each platform
Gerald Versluis has an amazing set of videos about .NET MAUI on YouTube. Several of them have been about publishing your project. I highly recommend checking out his channel, there is a ton of great MAUI content there. I'll list the publishing-specific topics ones here:
- Building .NET MAUI for Windows with GitHub Actions
- Publish .NET MAUI iOS project
- Publish your .NET MAUI Android project
- Publish an unpackaged (no MSIX) .NET MAUI Windows project
To help jumpstart your workflow commands, here are some publish commands that use environment secrets.
Example secrets and thier values (important, these should not be plain-text env vars)
- PFX_Certificate_Thumbprint: "MyMauiApp"
- PFX_FilePath: "MyCert.pfx"
- PFX_Password: "MyCertPassword"
dotnet publish MyApp.csproj -c Release -f:net6.0-windows10.0.19041.0 \
/p:GenerateAppxPackageOnBuild=true \
/p:AppxPackageSigningEnabled=true \
/p:PackageCertificateKeyFile=${{ secret.PFX_FilePath }} \
/p:PackageCertificatePassword=${{ secret.PFX_Password }} \
/p:PackageCertificateThumbprint=${{ secret.PFX_Certificate_Thumbprint }}
Example secrets and thier values (important, these should not be plain-text env vars)
- AndroidKeyStore: "john.keystore"
- AndroidKeyAlias: "john"
- AndroidKeyPass: "myKeyPassword"
- AndroidStorePass: "myStorePassword"
The publish command
dotnet publish -f net8.0-android -c Release \
-p:AndroidKeyStore=true \
-p:AndroidSigningKeyStore="${{ secrets.AndroidKeyStore }}" \
-p:AndroidSigningKeyAlias="${{ secrets.AndroidKeyAlias }}" \
-p:AndroidSigningKeyPass="${{ secrets.AndroidKeyPass }}" \
-p:AndroidSigningStorePass="${{ secrets.AndroidStorePass }}"
Example secrets and thier values (important, these should not be plain-text env vars)
- AppleCodesignProvisioningProfile: "MyMauiApp"
- AppleCodesignKey: "Apple Distribution: John Doe (ABCDE1234)"
- MacIpAddress: "192.168.1.100"
- MacPort: "58181"
- MacUsername: "JohnDoe"
- MacUserPassword: "JohnsMacPassword"
The publish command form a Windows machine, connecting to a Mac host (you can omit the server parameters if building on a Mac)
dotnet publish -f net8.0-ios -c Release \
-p:ArchiveOnBuild=true \
-p:RuntimeIdentifier=ios-arm64 \
-p:CodesignKey="${{ secrets.AppleCodesignKey }}" \
-p:CodesignProvision="${{ secrets.AppleCodesignProvisioningProfile }}" \
-p:ServerAddress="${{ secrets.MacIpAddress }}" \
-p:ServerUser="${{ secrets.MacUsername }}" \
-p:ServerPassword="${{ secrets.MacUserPassword }}" \
-p:TcpPort="${{ secrets.MacPort }}" \
-p:_DotNetRootRemoteDirectory="/Users/${{ secrets.MacUsername }}/Library/Caches/Xamarin/XMA/SDKs/dotnet/"
Example secrets and thier values (important, these should not be plain-text env vars)
- AppleCodesignProvisioningProfile: "MyMauiApp"
- AppleCodesignKey: "Apple Distribution: John Doe (ABCDE1234)"
- MacPackageSignKey: "3rd Party Mac Developer Installer: John Doe (ABCDE1234)"
The publish command form a Windows machine, connecting to a Mac host (you can omit the server parameters if building on a Mac)
dotnet publish -f net8.0-maccatalyst -c Release \
-p:MtouchLink=SdkOnly \
-p:CreatePackage=true \
-p:EnableCodeSigning=true \
-p:EnablePackageSigning=true \
-p:PackageSigningKey="${{ secrets.MacPackageSignKey }}" \
-p:CodesignKey="${{ secrets.AppleCodesignKey }}" \
-p:CodesignProvision="${{ secrets.AppleCodesignProvisioningProfile }}" \
-p:CodesignEntitlements="Platforms\MacCatalyst\Entitlements.plist"
If you are on a local device machine and have your certificates available, then you can also have conditional statements in the csproj file that will automatically apply these settings when in Release mode.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
... app stuff
<!-- Display name -->
<ApplicationTitle>MyApp</ApplicationTitle>
<!-- ****** Important App Identifiers ****** -->
<!-- Windows -->
<ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">com.lancelotsoftware.MyApp</ApplicationId>
<ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">61FFFFAC6-FFFFF-4830-FFFFF-78DFFF85CC</ApplicationIdGuid>
<!-- MacCatalyst -->
<ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">com.lancelotsoftware.MyApp</ApplicationId>
<ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">61FFFFC6-FFFF-4830-FFFF-78DFFFF85CC</ApplicationIdGuid>
<!-- iOS -->
<ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">com.lancelotsoftware.MyApp</ApplicationId>
<ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">61717AC6-FFFF-4830-FFFF-78DD35E085CC</ApplicationIdGuid>
<ProvisioningType Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">manual</ProvisioningType>
<CodesignKey Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">Apple Distribution: Lancelot Software, LLC (XXXXXXXXXX)</CodesignKey>
<!-- Android -->
<ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">com.lancelotsoftware.MyApp</ApplicationId>
<ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">617FFAC6-FFFF-4830-B715-78DFFFF85CC</ApplicationIdGuid>
<ProvisioningType Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">manual</ProvisioningType>
<CodesignKey Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">Apple Distribution: Lancelot Software, LLC (XXXXXXXXXX)</CodesignKey>
... target platform stuff
</PropertyGroup>
<ItemGroup>
... icons and assets
</ItemGroup>
<ItemGroup>
... nuget packages
</ItemGroup>
<!-- ******** Android ******** -->
<PropertyGroup Condition="$(TargetFramework.Contains('-android')) and '$(Configuration)' == 'Release'">
<AndroidKeyStore>True</AndroidKeyStore>
<AndroidSigningKeyStore>lance.keystore</AndroidSigningKeyStore>
<AndroidSigningStorePass>$(android_keystore_password)</AndroidSigningStorePass>
<AndroidSigningKeyAlias>lance.alias</AndroidSigningKeyAlias>
<AndroidSigningKeyPass>$(android_keystore_alias_password)</AndroidSigningKeyPass>
</PropertyGroup>
<!-- ******** iOS ******** -->
<PropertyGroup Condition="$(TargetFramework.Contains('-ios')) and '$(Configuration)' == 'Release'">
<RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
<CodesignKey>Apple Distribution: Lancelot Software, LLC (XXXXXXXXX)</CodesignKey>
<CodesignProvision>MyApp_AdHoc_2023.mobileprovision</CodesignProvision>
or for store
<CodesignProvision>MyApp_AppStore_2023.mobileprovision</CodesignProvision>
</PropertyGroup>
<!-- ******** MacCatalyst ******** -->
<PropertyGroup Condition="$(TargetFramework.Contains('-maccatalyst')) and '$(Configuration)' == 'Release'">
<RuntimeIdentifier>maccatalyst-x64</RuntimeIdentifier>
<CodesignKey>Apple Distribution: Lancelot Software, LLC (XXXXXXXXX)</CodesignKey>
<CodesignProvision>MyApp_MacStore_2023.provisionprofile</CodesignProvision>
</PropertyGroup>
<!-- ******** Windows (use VS2022 Publish tooling or CLI) ******** -->
</Project>