2025-08-06
Set Up Power Platform ALM with GitHub Actions

See every step. Control every secret. Ship the same app across dev → test → prod—on purpose.
What you’ll learn
-
Why Power Platform ALM breaks (hidden connector refs, env vars, drift, DLP)
-
How to structure GitHub Actions: triggers, jobs, environments, approvals
-
Secure identity & secrets: service principals, scoped env secrets, variable remapping
-
Build/test steps that catch issues early (solution validation, dependency checks)
-
A clean deploy pattern with rollback-ready artifacts
The ALM maze: what’s really different
-
Source ≠ code: Solutions bundle XML/JSON + hidden logic; changes aren’t visible until export.
-
Connectors are per-environment: References point to objects that don’t exist elsewhere by default.
-
Environment variables matter: Endpoints/keys must be swapped at deploy time, not baked in.
-
People-based deploys fail audits: Use service principals for consistency and traceability.
Core system: Source → Build → Test → Deploy (for Power Platform)
Source
-
Keep solutions in Git as unpacked (pac CLI) for diff-friendly PRs.
-
Commit both managed (release) and unmanaged (dev) exports; tag with solution version.
-
Store a
/configfolder per environment (env vars, connection ref mappings).
Build
-
Use GitHub Actions to pack/validate:
-
Unpack/pack with PAC CLI
-
Run Solution Checker (fail on criticals)
-
Generate dependency report (child flows, PCF, connection refs)
-
Publish artifacts: managed.zip + reports
-
Test
-
Spin up a test job against the target environment:
-
Verify env variables exist/are populated
-
Resolve connection refs to approved connectors
-
Optional “no-op” flow test runs (trigger test execution where safe)
-
Deploy
-
Import managed solution with service principal scoped to that environment.
-
Remap environment variables and connection references from
/config/test|prod. -
Post-deploy smoke checks (key flow run, app open, role access).
GitHub Actions: triggers, jobs, environments
Recommended triggers
-
pushtofeature/*→ build & validate only -
pull_requesttomain→ full build + solution checker + artifact -
workflow_dispatchonrelease/*→ deploy to test -
environment: productionwith required reviewers → deploy to prod
Job separation
-
build: pack, validate, solution checker, publish artifacts
-
prepare-env: fetch env config, map variables/refs
-
deploy-test/prod: import managed, apply mappings, smoke checks
Environment protection
-
Use GitHub Environments with scoped secrets:
DEV,TEST,PROD -
Require approvals for
PROD; restrict who can read those secrets -
Never reuse secrets across envs; name them with env prefixes (e.g.,
PROD_DATAVERSE_URL)
Secrets, service principals, and connectors
Service principals
-
One app registration per tenant; one SP per environment (least privilege).
-
Grant Dataverse roles sized for ALM tasks; store client ID/secret per env as GitHub secrets.
-
Rotate secrets; prefer federated credentials if available.
Environment variables
-
Store endpoints, table names, feature flags; never hardcode.
-
Maintain
/config/{env}/envvars.json; pipeline injects on import.
Connection references
-
Maintain
/config/{env}/connections.jsonmapping solution refs → target connectors. -
Validate existence & DLP compliance before import; fail fast if unresolved.
Guardrails that prevent “silent” breakage
-
Solution Checker gate (block on high/critical).
-
Dependency scan: ensure child flows/PCF/refs are included in the solution.
-
Config audit: ensure every required env var has a value in the target env.
-
DLP/Datasource check: block personal/unapproved connectors; enforce allow-lists.
-
Schema drift: compare Dataverse changes; require approval for destructive diffs.
Rollback & observability
Rollback
-
Always publish the previous managed.zip as an artifact; re-import on failure.
-
For severe issues, restore environment backup (Dataverse snapshot).
-
Practice restores in a sandbox; document timings and owners.
Observability
-
Log each step’s outputs (solution checker report, mapping summary, import result).
-
Emit deployment markers (run IDs, solution versions) to an audit table/dashboard.
-
Track KPIs: change failure rate, MTTR, % failures blocked pre-deploy.
Reference workflow outline (pseudo-YAML)
-
on: PR to main → build/validate; manualworkflow_dispatch→ deploy -
jobs.build: checkout → pac unpack/pack → solution checker → publish artifacts -
jobs.prepare-env: download artifacts → load/config/${{ env }}→ validate mappings -
jobs.deploy: use env-scoped secrets → pac auth (SP) → import managed → apply mappings → smoke tests
Common pitfalls (and fast fixes)
-
Works in dev, fails in test → missing connection ref mapping
-
Fix: add
/config/{env}/connections.json; validation step blocks if unresolved.
-
-
Wrong endpoint in prod → env vars not swapped
-
Fix: map on import; forbid hardcoded endpoints via linter script.
-
-
Audit issues → human account used for deploy
-
Fix: switch to SPs; restrict env secrets with approvals.
-
-
Unpack/pack drift → only commit unpacked; rebuild managed in CI.
-
Half imports → use “stage for upgrade” and auto-rollback to last artifact on failure.
Quick-start checklist (this week)
-
Create SPs + GitHub envs (DEV/TEST/PROD) with scoped secrets
-
Unpack your solution into Git; add Solution Checker to PRs
-
Add
/config/{env}/envvars.jsonandconnections.jsontemplates -
Build job: pack → validate → publish managed.zip
-
Deploy jobs: import with SP, remap vars/refs, smoke-test, approvals for PROD
-
Save previous managed.zip as rollback artifact; test a rollback in sandbox
Apple Podcasts · Spotify · YouTube
Host: Mirko Peters