Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
You can deploy Copilot Managed Runtime apps with GitHub Actions without going through the platform-managed build pipeline. Build each app with your own toolchain and deploy the external artifact directly through the Microsoft Copilot Managed Runtime CLI (ms).
Three GitHub Actions in the microsoft/Managed-Apps repository (under the github-actions/ path) do the work:
install-ms-cliinstalls the Copilot Managed Runtime CLI on the runner.ms-app-packbuilds the app and packs it into a deployable artifact.ms-app-deploydeploys the artifact to your environment.
After you complete the steps in this article, every push to your repository's main branch automatically deploys to your environment.
Note
External artifact deployment is for apps that don't use the default platform-managed build and deploy pipeline. An app that uses external artifact deployment has "repoType": "none" in its ms.config.json.
Prerequisites
Before you deploy an app from GitHub Actions:
- Create an app with
"repoType": "none". Runms app create--repo nonelocally to create the app and generate itsms.config.json(stamped with"repoType": "none"). This step needs the@microsoft/managed-apps-cliversion0.7.0or later installed locally; the workflow installs the CLI on the runner for you. - Use a GitHub repository that contains your app source (including
ms.config.json) and the workflow file. You need permission to manage the repository's Actions secrets. - Configure a service principal with permission on the app's environment. You create one in Step 1.
- External artifact deployment is disallowed by default. See External artifact deployment.
Important
Your organization is responsible for validating externally built artifacts and securing the pipeline to meet its security, compliance, and software supply-chain requirements.
Step 1: Create and configure the service principal
The workflow authenticates as a service principal by using the OAuth 2.0 client credentials flow. Configure the service principal once per tenant.
Register an application in Microsoft Entra ID
- Sign in to the Azure portal.
- Go to Microsoft Entra ID > App registrations > + New registration.
- Enter a name (for example,
managed-apps-spn), choose Accounts in this organizational directory only, leave the redirect URI blank, and then select Register. - On the Overview page, copy the Application (client) ID and Directory (tenant) ID for later.
- Go to Certificates & secrets > + New client secret. Enter a description, choose an expiry, and then select Add. Immediately copy the Value column. You can't see the secret again after you leave this page.
Grant the service principal permission on the app's environment
The exact grant depends on whether the environment is Dataverse-enabled.
Dataverse-enabled environments
- Sign in to the Power Platform admin center.
- Select Environments, and then select the app's environment.
- Go to Settings > Users + permissions > Application users.
- Select + New app user, select + Add an app, search by the application (client) ID, and then select Add.
- Set the business unit, assign the System Administrator and System Customizer security roles, and then select Create.
Non-Dataverse environments
Non-Dataverse (sandbox-SKU) environments don't host application users. Grant the EnvironmentAdmin role directly through the Business Application Platform (BAP) API.
Important
Use the service principal's object ID (from Azure portal > Enterprise applications > your app > Overview), not the app registration's object ID. They're different GUIDs, and using the wrong one returns a 400 Principal not found error.
Get an access token from a user who has the EnvironmentAdmin role on the app's environment:
az login --tenant <your-tenant-id>
az account get-access-token --resource https://service.powerapps.com/ --query accessToken -o tsv
Then issue a POST request to the BAP API to add the role assignment. For production, use https://api.bap.microsoft.com as the base URL.
POST https://<bap-base-url>/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments/<environment-id>/modifyRoleAssignments?api-version=2021-04-01
Authorization: Bearer <user-token>
Content-Type: application/json
{
"add": [
{
"properties": {
"roleDefinition": {
"id": "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments/<environment-id>/roleDefinitions/EnvironmentAdmin"
},
"principal": {
"id": "<service-principal-object-id>",
"type": "ServicePrincipal",
"tenantId": "<tenant-id>"
}
}
}
],
"remove": []
}
Note
Instead of calling the BAP API directly, you can use the Power Platform for Admins connector's Edit Environment Role Assignment action, which is convenient when you grant the role from a Power Automate cloud flow. For programmatic access to the same admin capabilities, see Get started with the Power Platform API.
Step 2: Add the secrets to your GitHub repository
In your repository, select Settings > Secrets and variables > Actions > New repository secret, and then add these three secrets from the app registration:
| Secret name | Value |
|---|---|
PP_SP_CLIENT_ID |
Application (client) ID. |
PP_SP_CLIENT_SECRET |
Client secret value. |
PP_SP_TENANT_ID |
Directory (tenant) ID. |
Step 3: Add the deployment workflow
Add the following workflow to your repository at .github/workflows/deploy-managed-app.yml. Replace apps/my-app with the path to your app.
name: Deploy Copilot Managed Runtime app
on:
push:
branches: [main]
paths:
- 'apps/my-app/**'
- '.github/workflows/deploy-managed-app.yml'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup Node 24
uses: actions/setup-node@v5
with:
node-version: '24'
- name: Install app dependencies
working-directory: apps/my-app
run: npm install
- name: Install ms CLI
uses: microsoft/Managed-Apps/github-actions/install-ms-cli@v1
- name: Pack app
uses: microsoft/Managed-Apps/github-actions/ms-app-pack@v1
with:
working-directory: 'apps/my-app'
app-id: ${{ secrets.PP_SP_CLIENT_ID }}
client-secret: ${{ secrets.PP_SP_CLIENT_SECRET }}
tenant-id: ${{ secrets.PP_SP_TENANT_ID }}
- name: Deploy app
uses: microsoft/Managed-Apps/github-actions/ms-app-deploy@v1
with:
working-directory: 'apps/my-app'
app-id: ${{ secrets.PP_SP_CLIENT_ID }}
client-secret: ${{ secrets.PP_SP_CLIENT_SECRET }}
tenant-id: ${{ secrets.PP_SP_TENANT_ID }}
The paths: filter runs the workflow only when files inside the app's directory change. In a monorepo with multiple apps, give each app its own workflow file and paths: filter.
Important
The npm install step must run in your app's working directory before the pack step. ms-app-pack builds the app by running npm run build, which requires node_modules to be present. Use the same working-directory for npm install, ms-app-pack, and ms-app-deploy.
That's all you need for the standard workflow: ms-app-pack builds and stages the artifact, and ms-app-deploy deploys it. The next section covers the actions in more detail, including how to deploy a prebuilt artifact.
Reference: the GitHub Actions
Reference each action as microsoft/Managed-Apps/github-actions/<name>@v1. The v1 tag moves to the latest release; pin an immutable tag such as @v1.0.1 for reproducible builds.
ms-app-pack and ms-app-deploy authenticate with the service principal, so pass these required inputs to each action from the secrets you added in Step 2:
| Input | Description |
|---|---|
app-id |
Service principal client ID. |
client-secret |
Service principal secret. |
tenant-id |
Microsoft Entra ID tenant ID. |
install-ms-cli
Installs @microsoft/managed-apps-cli onto the runner and adds the ms binary to the PATH. It works with no inputs for public npm. To install from a private feed (for example, Azure DevOps Artifacts) or to pin a version, set the optional inputs:
| Name | Default | Description |
|---|---|---|
version |
latest |
Semver or latest. Pin a specific version for reproducibility. |
registry-url |
https://registry.npmjs.org |
npm registry URL. Override it for internal feeds. |
registry-auth-token |
(empty) | Bearer token or PAT for private registries. |
npm-package-name |
@microsoft/managed-apps-cli |
Override it for testing alternate builds. |
The action outputs cli-version, the installed CLI version.
ms-app-pack
Builds and packs the app by running ms app pack. Pack runs the buildCommand from ms.config.json (defaults to npm run build) and stages the artifact under <working-directory>/.ms/packed/. Use this action before ms-app-deploy so build and packaging failures are visible as their own workflow step.
Set working-directory (optional, defaults to the repo root) to the directory that contains ms.config.json and package.json, plus the three service principal inputs.
ms-app-deploy
Deploys to the app's environment by running ms app deploy. The action reads appId, environmentId, and repoType from ms.config.json and selects the deploy mode automatically. To deploy a prebuilt artifact and skip the build, set artifact-path:
- uses: microsoft/Managed-Apps/github-actions/ms-app-deploy@v1
with:
working-directory: 'apps/my-app'
artifact-path: '../app.zip'
app-id: ${{ secrets.PP_SP_CLIENT_ID }}
client-secret: ${{ secrets.PP_SP_CLIENT_SECRET }}
tenant-id: ${{ secrets.PP_SP_TENANT_ID }}
Optional inputs, in addition to the three service principal inputs:
| Name | Default | Description |
|---|---|---|
working-directory |
(repo root) | Path to the directory that contains ms.config.json. |
commit-sha |
$GITHUB_SHA |
Commit SHA for git-backed apps. Mutually exclusive with artifact-path. |
artifact-path |
(empty) | Path to a prebuilt .zip artifact. Valid only for repoType: 'none' apps. Mutually exclusive with commit-sha. |
app-name |
(from ms.config.json) |
Override the app name. |
The action outputs app-id, environment-id, commit-sha, and app-play-uri for the deployment.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| The action reports that it isn't signed in. | The action didn't receive all three service principal inputs. | Pass app-id, client-secret, and tenant-id to both ms-app-pack and ms-app-deploy. |
External artifact deployment is disabled for this environment |
External artifact deployment isn't enabled. | See External artifact deployment. |
Forbidden — 'Repositories.MicrosoftApps.Deploy.Write' (Dataverse environment) |
The service principal is missing the required Dataverse roles. | Assign both the System Administrator and System Customizer roles to the application user. |
400 Principal not found (non-Dataverse environment) |
You used the app registration's object ID instead of the service principal's object ID. | Use the object ID from Enterprise applications, not from App registrations. |
ms.config.json not found in working-directory |
working-directory doesn't point to the directory that contains ms.config.json. |
Set working-directory to that path. The paths: filter, npm install step, and the actions must all use the same path. |
npm error code E401 Incorrect or missing password |
A private registry is configured, but the auth token has the wrong scope or organization. | Make sure registry-auth-token is a PAT with the right scope and organization for that feed. |