HomeCI/CD & DevOpsStep-by-Step: CI/CD for .NET MAUI with GitHub Actions

Step-by-Step: CI/CD for .NET MAUI with GitHub Actions

With .NET MAUI (Multi-platform App UI), developers can build native mobile and desktop apps using a single codebase for Android, iOS, macOS, and Windows. While the framework simplifies app development, building and publishing apps to the App Store and Play Store can still be a time-consuming manual process.

This is where continuous integration and delivery (CI/CD) pipelines come into play. CI/CD pipelines automate building, testing, and publishing your apps, allowing you to focus on writing code rather than managing builds. GitHub Actions, GitHub’s own automation tool, is a powerful solution to automate these tasks.

In this comprehensive guide, I’ll walk you through setting up a CI/CD pipeline using GitHub Actions to automatically build your .NET MAUI app and publish it to both the Apple App Store and Google Play Store.

What is GitHub Actions?

GitHub Actions is a CI/CD tool that integrates directly with your GitHub repositories. It allows you to automate tasks like building, testing, and deploying applications based on events such as commits, pull requests, or scheduled workflows.

Prerequisites

Before getting started, make sure you have the following:

    • GitHub account: Your project should be hosted in a GitHub repository
    • Apple Developer account: You'll need this to publish to the Apple App Store
    • Google Play Developer account: Necessary for publishing to the Play Store
    • Certificates: Ensure Xcode (for iOS), and any necessary signing certificates (e.g., Android keystore, iOS provisioning profiles) are accessible
    • App Setup: You have to set up your apps in the App Store and Play Store already

Complete iOS App Store Deployment Setup

Step 1: Configure macOS Build Environment

The iOS deployment workflow requires a macOS runner since Xcode is only available on macOS. This step sets up all the necessary tools including Xcode, .NET SDK, and the MAUI workload for cross-platform development.

    runs-on: macos-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup Xcode version
      uses: maxim-lobanov/setup-xcode@v1.6.0
      with:
        xcode-version: 16.0
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 8.0.x
    - name: Install MAUI workload
      run: dotnet workload install maui

Key Configuration Points:

    • macos-latest: The workflow runs on a macOS machine, which is required for iOS builds
    • Checkout repository: It checks out your code from the repository so that the following steps can access it
    • Setup Xcode: This step installs Xcode (version 16.0), which is essential for building iOS apps
    • Install .NET SDK: This step installs the .NET 8.0 SDK
    • Install MAUI workload: This ensures the MAUI workload is available for building the project

Step 2: Import iOS Certificates and Provisioning Profiles

iOS apps require proper code-signing certificates and provisioning profiles for deployment. This section handles the secure import of your Apple Developer certificates and automatically downloads the correct provisioning profiles for your app.

- name: Import Code-Signing Certificates
  uses: Apple-Actions/import-codesign-certs@v1
  with:
    p12-filepath: 'AppleSigningCertificate.p12'
    p12-password: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
    
- name: Download Apple Provisioning Profiles
  uses: Apple-Actions/download-provisioning-profiles@v1
  with:
    bundle-id: 'com.yourcompany.yourapp'
    issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
    api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
    api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

Certificate Management Process:

iOS apps require code-signing to be deployed, and this section handles importing the necessary certificates and provisioning profiles:

Import Code-Signing Certificates: It uses a .p12 certificate for code-signing, which is required by Apple. The p12-password is securely fetched from GitHub secrets.

Download Apple Provisioning Profiles: It fetches the required provisioning profile based on your app’s bundle identifier, using credentials stored in GitHub secrets.

    • The Bundle-Id is your app identifier
    • Generate an API Key following Apple’s documentation: Creating API Keys for App Store Connect API
    • Choose Team Key and download it. Copy the ID and put it in GitHub Actions Secret (APPSTORE_KEY_ID)
    • Copy the content of your key and put it in a Secret (APPSTORE_PRIVATE_KEY)
    • Get your Issuer ID for the Secret (APPSTORE_ISSUER_ID): FastLane Discussion

Step 3: Build iOS App and Upload to TestFlight

This final step compiles your MAUI project for iOS and automatically uploads the resulting IPA file to TestFlight for testing and eventual App Store submission.

- name: Build iOS App
  run: |
    dotnet publish YourApp/YourApp.csproj \
      -f net8.0-ios \
      -c Release \
      -p:ArchiveOnBuild=true \
      -p:EnableAssemblyILStripping=false
      
- name: Upload app to TestFlight
  uses: Apple-Actions/upload-testflight-build@v1
  with:
    app-path: 'YourApp/bin/Release/net8.0-ios/publish/YourApp.ipa'
    issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
    api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
    api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

Build and Deployment Process:

This section builds your MAUI project for iOS:

    • Build: The dotnet publish command compiles the app for the iOS platform using the .NET 8.0 framework in Release configuration. The additional properties ensure the app builds successfully
    • Upload to TestFlight: This step uploads the .ipa file (iOS app archive) to TestFlight using Apple’s API credentials securely stored in GitHub secrets

Complete iOS Workflow Configuration

Here's the complete iOS workflow that combines all the steps above into a single, ready-to-use GitHub Actions job:

build-ios:
  runs-on: macos-latest
  steps:
  - uses: actions/checkout@v3
    
  - name: Setup Xcode version
    uses: maxim-lobanov/setup-xcode@v1.6.0
    with:
      xcode-version: 16.0
      
  - name: Setup .NET
    uses: actions/setup-dotnet@v3
    with:
      dotnet-version: 8.0.x
      
  - name: Install MAUI workload
    run: dotnet workload install maui
    
  - name: Import Code-Signing Certificates
    uses: Apple-Actions/import-codesign-certs@v1
    with:
      p12-filepath: 'AppleSigningCertificate.p12'
      p12-password: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
      
  - name: Download Apple Provisioning Profiles
    uses: Apple-Actions/download-provisioning-profiles@v1
    with:
      bundle-id: 'com.yourcompany.yourapp'
      issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
      api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
      api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
      
  - name: Build iOS App
    run: |
      dotnet publish YourApp/YourApp.csproj \
        -f net8.0-ios \
        -c Release \
        -p:ArchiveOnBuild=true \
        -p:EnableAssemblyILStripping=false
    
  - name: Upload app to TestFlight
    uses: Apple-Actions/upload-testflight-build@v1
    with:
      app-path: 'YourApp/bin/Release/net8.0-ios/publish/YourApp.ipa'
      issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
      api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
      api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

Complete Android Play Store Deployment Setup

Step 1: Configure Windows Build Environment

Android MAUI apps can be built on Windows runners, which are often faster and more cost-effective than macOS runners. This step prepares the Windows environment with all necessary tools for Android development.

runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
  uses: actions/setup-dotnet@v3
  with:
    dotnet-version: 8.0.x
- name: Install MAUI workload
  run: dotnet workload install maui

Environment Configuration:

The Android workflow sets up the environment required for building your MAUI Android app:

    • windows-latest: The workflow runs on a Windows machine, as Windows is commonly used for Android development
    • Checkout repository: It checks out your code from the GitHub repository so the subsequent steps can access it
    • Setup .NET: This step installs the .NET 8.0 SDK
    • Install MAUI workload: Installs the MAUI workload necessary for Android app development

Step 2: Build and Sign Your Android App

This step compiles your MAUI project for Android and signs it with your keystore for Play Store deployment. Proper signing is essential for app store acceptance and user security.

- name: Build and Sign Android App
  run: |
    dotnet publish YourApp/YourApp.csproj \
      -f net8.0-android \
      -c Release \
      -p:AndroidKeyStore=true \
      -p:AndroidSigningKeyStore=your-app.keystore \
      -p:AndroidSigningKeyAlias=your-alias \
      -p:AndroidSigningKeyPass="${{ secrets.KEYSTORE_PASSWORD }}" \
      -p:AndroidSigningStorePass="${{ secrets.KEYSTORE_PASSWORD_ALIAS }}"

Android Build and Signing Configuration:

Build command: The dotnet publish command builds the MAUI Android project for the net8.0-android framework in Release mode.

Signing parameters: The command uses key properties to enable Android signing:

    • AndroidKeyStore=true: Enables the use of the Android keystore for signing
    • Keystore path and alias: The app is signed with the specified keystore and alias
    • Passwords: The keystore and alias passwords are securely fetched from GitHub secrets

⚠️ Security Note: Storing the keystore file directly in your repository is not the most secure approach. For better security practices, consider encoding the keystore as base64 and storing it in GitHub Secrets, then decoding it during the build process.

Learn more about Android keystore management: YouTube Tutorial

Step 3: Upload to Google Play Console

The final step automatically uploads your signed Android App Bundle to the Google Play Console, making it available for internal testing or release preparation.

- name: Upload to Google Play Console
  uses: r0adkll/upload-google-play@v1.1.3
  with:
    serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_SERVICE_ACCOUNT }}
    packageName: com.yourcompany.yourapp
    releaseFiles: YourApp/bin/Release/net8.0-android/publish/com.yourcompany.yourapp-Signed.aab
    track: internal

Google Play Upload Configuration:

This part automates the upload of the signed Android app to the Google Play Console:

    • Google Play upload action: Uses the r0adkll/upload-google-play action to upload the Android App Bundle (.aab) to the Play Console
    • Service account: The Play Store service account credentials are securely passed as a GitHub secret
    • Package name: Specifies the app’s unique package name, which identifies the app on the Play Store
    • Release file: Points to the signed Android App Bundle that was built in the previous step
    • Track: The app is uploaded to the internal track for internal testing or deployment

Setting up Google Play Service Account: Follow the official documentation to configure access via service account.

Complete Android Workflow Configuration

Here's the complete Android workflow that combines all steps into a production-ready GitHub Actions job:

build-android:
  runs-on: windows-latest
  steps:
  - uses: actions/checkout@v3
  
  - name: Setup .NET
    uses: actions/setup-dotnet@v3
    with:
      dotnet-version: 8.0.x
      
  - name: Install MAUI workload
    run: dotnet workload install maui
    
  - name: Build and Sign Android App
    run: |
      dotnet publish YourApp/YourApp.csproj \
        -f net8.0-android \
        -c Release \
        -p:AndroidKeyStore=true \
        -p:AndroidSigningKeyStore=your-app.keystore \
        -p:AndroidSigningKeyAlias=your-alias \
        -p:AndroidSigningKeyPass="${{ secrets.KEYSTORE_PASSWORD }}" \
        -p:AndroidSigningStorePass="${{ secrets.KEYSTORE_PASSWORD_ALIAS }}"
        
  - name: Upload to Google Play Console
    uses: r0adkll/upload-google-play@v1.1.3
    with:
      serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_SERVICE_ACCOUNT }}
      packageName: com.yourcompany.yourapp
      releaseFiles: YourApp/bin/Release/net8.0-android/publish/com.yourcompany.yourapp-Signed.aab
      track: internal

Production-Ready Complete Workflow

This comprehensive workflow file combines both iOS and Android deployment pipelines into a single, production-ready GitHub Actions configuration that you can use immediately in your MAUI project:

name: MAUI CI/CD Pipeline

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build-android:
    runs-on: windows-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 8.0.x
        
    - name: Install MAUI workload
      run: dotnet workload install maui
      
    - name: Build and Sign Android App
      run: |
        dotnet publish YourApp/YourApp.csproj \
          -f net8.0-android \
          -c Release \
          -p:AndroidKeyStore=true \
          -p:AndroidSigningKeyStore=your-app.keystore \
          -p:AndroidSigningKeyAlias=your-alias \
          -p:AndroidSigningKeyPass="${{ secrets.KEYSTORE_PASSWORD }}" \
          -p:AndroidSigningStorePass="${{ secrets.KEYSTORE_PASSWORD_ALIAS }}"
          
    - name: List build output
      run: ls -R ./**/*-Signed.aab
      
    - name: Upload to Google Play Console
      uses: r0adkll/upload-google-play@v1.1.3
      with:
        serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_SERVICE_ACCOUNT }}
        packageName: com.yourcompany.yourapp
        releaseFiles: YourApp/bin/Release/net8.0-android/publish/com.yourcompany.yourapp-Signed.aab
        track: internal

  build-ios:
    runs-on: macos-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Xcode version
      uses: maxim-lobanov/setup-xcode@v1.6.0
      with:
        xcode-version: 16.0
        
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 8.0.x
        
    - name: Install MAUI workload
      run: dotnet workload install maui
      
    - name: Import Code-Signing Certificates
      uses: Apple-Actions/import-codesign-certs@v1
      with:
        p12-filepath: 'AppleSigningCertificate.p12'
        p12-password: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
        
    - name: Download Apple Provisioning Profiles
      uses: Apple-Actions/download-provisioning-profiles@v1
      with:
        bundle-id: 'com.yourcompany.yourapp'
        issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
        api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
        api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
        
    - name: Build iOS App
      run: |
        dotnet publish YourApp/YourApp.csproj \
          -f net8.0-ios \
          -c Release \
          -p:ArchiveOnBuild=true \
          -p:EnableAssemblyILStripping=false
          
    - name: List build output
      run: ls -R YourApp/bin/Release/net8.0-ios/publish/
      
    - name: Upload app to TestFlight
      uses: Apple-Actions/upload-testflight-build@v1
      with:
        app-path: 'YourApp/bin/Release/net8.0-ios/publish/YourApp.ipa'
        issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
        api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
        api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

Required GitHub Secrets Checklist

Before running your GitHub Actions workflow, ensure you have configured all the required secrets in your repository settings:

iOS Deployment Secrets

  • APPLE_SIGNING_CERTIFICATE_PASSWORD - Password for your .p12 certificate file
  • APPSTORE_ISSUER_ID - Your App Store Connect API issuer ID
  • APPSTORE_KEY_ID - Your App Store Connect API key ID
  • APPSTORE_PRIVATE_KEY - Content of your App Store Connect API private key (.p8 file)

Android Deployment Secrets

  • KEYSTORE_PASSWORD - Password for your Android keystore file
  • KEYSTORE_PASSWORD_ALIAS - Password for your keystore alias
  • PLAYSTORE_SERVICE_ACCOUNT - JSON content of your Google Play service account

To add secrets to your GitHub repository:

    1. Go to your repository → Settings → Secrets and Variables → Actions
    2. Click "New repository secret"
    3. Add the secret name and value
    4. Save the secret

Make sure all secrets are properly configured before triggering your first deployment!

Key Benefits of This Automated Approach

    • Automated Deployments: No more manual builds and uploads
    • Consistent Environment: Every build uses the same configuration
    • Time Savings: Focus on development instead of deployment tasks
    • Error Reduction: Automated processes reduce human error
    • Version Control: All deployment configurations are version controlled

Common Challenges and Solutions

    1. Certificate Management: Ensure all certificates are valid and properly configured
    2. Secrets Configuration: Double-check that all GitHub Secrets are correctly set
    3. Path Issues: Verify that file paths in your workflow match your project structure
    4. Platform Differences: Remember that iOS requires macOS runners while Android can use Windows or Linux

Conclusion

Implementing CI/CD for .NET MAUI applications with GitHub Actions significantly improves your development workflow. This automated approach saves time, reduces errors, and ensures consistent deployments across both major mobile platforms.

The initial setup requires careful attention to certificate management and secret configuration, but once established, you'll have a robust deployment pipeline that scales with your development needs.

Start with this basic configuration and gradually enhance it with additional features like automated testing, conditional deployments, and notification systems as your project grows.

No comments yet! You be the first to comment.

Leave a Reply

Your email address will not be published. Required fields are marked *