Setting Up ALM for Power Platform with GitHub Actions


This episode demystifies Power Platform ALM with GitHub Actions so you can see—and control—every step from source to prod. Learn why deployments fail (connector references, environment variables, and human-led imports), how to wire service principals and scoped secrets, and how to structure GitHub workflows (triggers, jobs, env-specific vaults) that validate, remap, and deploy solutions predictably. We cover source management with unpacked solutions, build-time checks, connector/variable remapping at deploy, and guardrails that stop silent breakages. If your Power Apps and flows “work in dev” but die in test or prod, this is your step-by-step playbook to ship reliable, auditable releases—without guesswork.
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 onlypull_requesttomain→ full build + solution checker + artifactworkflow_dispatchonrelease/*→ deploy to testenvironment: 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,PRODRequire approvals for
PROD; restrict who can read those secretsNever 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→ deployjobs.build: checkout → pac unpack/pack → solution checker → publish artifactsjobs.prepare-env: download artifacts → load/config/${{ env }}→ validate mappingsjobs.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.jsontemplatesBuild 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
🚀 Want to be part of m365.fm?
Then stop just listening… and start showing up.
👉 Connect with me on LinkedIn and let’s make something happen:
- 🎙️ Be a podcast guest and share your story
- 🎧 Host your own episode (yes, seriously)
- 💡 Pitch topics the community actually wants to hear
- 🌍 Build your personal brand in the Microsoft 365 space
This isn’t just a podcast — it’s a platform for people who take action.
🔥 Most people wait. The best ones don’t.
👉 Connect with me on LinkedIn and send me a message:
"I want in"
Let’s build something awesome 👊
1
00:00:00.080 --> 00:00:03.000
2
00:00:03.160 --> 00:00:05.759
3
00:00:05.799 --> 00:00:08.599
4
00:00:08.599 --> 00:00:12.160
5
00:00:12.240 --> 00:00:16.000
6
00:00:16.239 --> 00:00:20.199
7
00:00:20.320 --> 00:00:25.000
8
00:00:25.160 --> 00:00:27.839
9
00:00:27.960 --> 00:00:31.399
10
00:00:31.399 --> 00:00:34.439
11
00:00:34.479 --> 00:00:37.640
12
00:00:37.679 --> 00:00:40.600
13
00:00:40.920 --> 00:00:45.079
14
00:00:45.079 --> 00:00:48.359
15
00:00:48.399 --> 00:00:51.920
16
00:00:52.000 --> 00:00:55.719
17
00:00:55.719 --> 00:00:58.960
18
00:00:59.079 --> 00:01:02.079
19
00:01:02.079 --> 00:01:05.120
20
00:01:05.159 --> 00:01:07.959
21
00:01:08.079 --> 00:01:12.079
22
00:01:12.200 --> 00:01:14.879
23
00:01:14.959 --> 00:01:17.599
24
00:01:17.680 --> 00:01:20.879
25
00:01:21.000 --> 00:01:23.640
26
00:01:24.439 --> 00:01:27.680
27
00:01:27.719 --> 00:01:30.879
28
00:01:30.920 --> 00:01:34.040
29
00:01:34.120 --> 00:01:37.560
30
00:01:37.640 --> 00:01:40.719
31
00:01:40.760 --> 00:01:43.799
32
00:01:43.799 --> 00:01:47.840
33
00:01:47.959 --> 00:01:50.799
34
00:01:50.799 --> 00:01:53.400
35
00:01:53.439 --> 00:01:56.040
36
00:01:56.079 --> 00:01:59.560
37
00:01:59.599 --> 00:02:02.480
38
00:02:02.519 --> 00:02:05.799
39
00:02:05.799 --> 00:02:09.680
40
00:02:09.879 --> 00:02:14.240
41
00:02:14.280 --> 00:02:17.759
42
00:02:17.800 --> 00:02:20.400
43
00:02:20.400 --> 00:02:23.719
44
00:02:23.759 --> 00:02:28.680
45
00:02:29.240 --> 00:02:32.800
46
00:02:33.000 --> 00:02:35.719
47
00:02:35.800 --> 00:02:39.520
48
00:02:39.680 --> 00:02:44.439
49
00:02:44.479 --> 00:02:48.080
50
00:02:48.240 --> 00:02:51.479
51
00:02:51.520 --> 00:02:55.479
52
00:02:55.520 --> 00:02:58.759
53
00:02:58.800 --> 00:03:01.719
54
00:03:01.719 --> 00:03:04.080
55
00:03:04.159 --> 00:03:06.919
56
00:03:07.000 --> 00:03:10.400
57
00:03:10.400 --> 00:03:12.719
58
00:03:12.800 --> 00:03:15.560
59
00:03:15.560 --> 00:03:19.120
60
00:03:19.159 --> 00:03:22.319
61
00:03:22.400 --> 00:03:26.159
62
00:03:26.199 --> 00:03:30.360
63
00:03:30.400 --> 00:03:33.919
64
00:03:33.960 --> 00:03:36.800
65
00:03:36.919 --> 00:03:41.719
66
00:03:42.360 --> 00:03:45.400
67
00:03:45.479 --> 00:03:48.919
68
00:03:48.960 --> 00:03:52.080
69
00:03:52.159 --> 00:03:55.840
70
00:03:55.879 --> 00:03:59.479
71
00:03:59.599 --> 00:04:03.159
72
00:04:03.199 --> 00:04:08.120
73
00:04:08.199 --> 00:04:11.759
74
00:04:11.840 --> 00:04:15.879
75
00:04:15.919 --> 00:04:18.759
76
00:04:18.800 --> 00:04:21.639
77
00:04:21.639 --> 00:04:25.079
78
00:04:25.120 --> 00:04:29.519
79
00:04:29.519 --> 00:04:33.120
80
00:04:33.120 --> 00:04:36.720
81
00:04:36.759 --> 00:04:39.759
82
00:04:39.800 --> 00:04:42.000
83
00:04:42.000 --> 00:04:45.839
84
00:04:45.879 --> 00:04:48.800
85
00:04:48.879 --> 00:04:52.000
86
00:04:52.120 --> 00:04:57.240
87
00:04:57.240 --> 00:05:01.120
88
00:05:01.160 --> 00:05:04.240
89
00:05:04.279 --> 00:05:08.600
90
00:05:08.639 --> 00:05:13.079
91
00:05:13.199 --> 00:05:16.480
92
00:05:16.519 --> 00:05:21.319
93
00:05:21.439 --> 00:05:24.879
94
00:05:24.879 --> 00:05:28.839
95
00:05:29.160 --> 00:05:32.319
96
00:05:32.360 --> 00:05:35.240
97
00:05:35.240 --> 00:05:38.720
98
00:05:38.759 --> 00:05:42.439
99
00:05:42.480 --> 00:05:44.839
100
00:05:45.000 --> 00:05:48.000
101
00:05:48.040 --> 00:05:51.240
102
00:05:51.319 --> 00:05:54.399
103
00:05:54.439 --> 00:05:59.879
104
00:06:00.279 --> 00:06:03.399
105
00:06:03.600 --> 00:06:06.040
106
00:06:06.079 --> 00:06:10.759
107
00:06:10.800 --> 00:06:13.639
108
00:06:13.720 --> 00:06:17.439
109
00:06:17.480 --> 00:06:21.720
110
00:06:21.839 --> 00:06:26.639
111
00:06:26.680 --> 00:06:29.720
112
00:06:29.959 --> 00:06:32.439
113
00:06:32.439 --> 00:06:35.199
114
00:06:35.240 --> 00:06:37.160
115
00:06:37.199 --> 00:06:40.680
116
00:06:40.680 --> 00:06:44.040
117
00:06:44.120 --> 00:06:47.279
118
00:06:47.480 --> 00:06:51.360
119
00:06:51.360 --> 00:06:54.000
120
00:06:54.079 --> 00:06:57.519
121
00:06:57.560 --> 00:07:00.439
122
00:07:00.439 --> 00:07:04.120
123
00:07:04.160 --> 00:07:08.160
124
00:07:08.240 --> 00:07:11.319
125
00:07:11.319 --> 00:07:14.879
126
00:07:14.879 --> 00:07:19.639
127
00:07:19.720 --> 00:07:23.959
128
00:07:24.439 --> 00:07:28.759
129
00:07:29.160 --> 00:07:31.639
130
00:07:31.639 --> 00:07:35.879
131
00:07:36.000 --> 00:07:38.360
132
00:07:38.439 --> 00:07:42.560
133
00:07:43.079 --> 00:07:46.639
134
00:07:46.639 --> 00:07:49.600
135
00:07:49.639 --> 00:07:53.279
136
00:07:53.360 --> 00:07:57.399
137
00:07:57.439 --> 00:08:00.879
138
00:08:01.040 --> 00:08:05.399
139
00:08:05.439 --> 00:08:08.240
140
00:08:08.240 --> 00:08:11.199
141
00:08:11.279 --> 00:08:15.040
142
00:08:15.079 --> 00:08:18.000
143
00:08:18.360 --> 00:08:22.519
144
00:08:22.920 --> 00:08:26.000
145
00:08:26.160 --> 00:08:30.480
146
00:08:30.959 --> 00:08:33.200
147
00:08:33.240 --> 00:08:36.600
148
00:08:36.639 --> 00:08:39.399
149
00:08:39.759 --> 00:08:43.440
150
00:08:43.840 --> 00:08:47.039
151
00:08:47.039 --> 00:08:50.559
152
00:08:50.559 --> 00:08:53.559
153
00:08:53.559 --> 00:08:58.039
154
00:08:58.080 --> 00:09:01.519
155
00:09:01.559 --> 00:09:05.240
156
00:09:05.360 --> 00:09:09.240
157
00:09:09.399 --> 00:09:13.279
158
00:09:13.279 --> 00:09:16.360
159
00:09:16.399 --> 00:09:19.879
160
00:09:20.159 --> 00:09:24.399
161
00:09:24.399 --> 00:09:27.120
162
00:09:27.120 --> 00:09:30.600
163
00:09:30.639 --> 00:09:32.919
164
00:09:33.000 --> 00:09:36.519
165
00:09:36.559 --> 00:09:41.159
166
00:09:41.159 --> 00:09:44.279
167
00:09:44.559 --> 00:09:48.679
168
00:09:48.679 --> 00:09:51.720
169
00:09:51.840 --> 00:09:54.840
170
00:09:54.960 --> 00:09:57.159
171
00:09:57.200 --> 00:09:59.840
172
00:09:59.840 --> 00:10:03.000
173
00:10:03.039 --> 00:10:06.080
174
00:10:06.120 --> 00:10:08.039
175
00:10:08.639 --> 00:10:12.080
176
00:10:12.120 --> 00:10:15.480
177
00:10:15.639 --> 00:10:19.039
178
00:10:19.039 --> 00:10:22.600
179
00:10:22.600 --> 00:10:25.120
180
00:10:25.120 --> 00:10:28.679
181
00:10:28.720 --> 00:10:32.679
182
00:10:33.080 --> 00:10:36.799
183
00:10:36.799 --> 00:10:40.799
184
00:10:40.840 --> 00:10:43.600
185
00:10:43.600 --> 00:10:45.960
186
00:10:45.960 --> 00:10:48.960
187
00:10:49.120 --> 00:10:52.039
188
00:10:52.080 --> 00:10:54.799
189
00:10:54.799 --> 00:10:58.080
190
00:10:58.120 --> 00:11:02.159
191
00:11:02.320 --> 00:11:06.679
192
00:11:06.759 --> 00:11:10.399
193
00:11:10.519 --> 00:11:14.279
194
00:11:14.320 --> 00:11:17.240
195
00:11:17.240 --> 00:11:20.159
196
00:11:20.200 --> 00:11:23.399
197
00:11:24.080 --> 00:11:26.799
198
00:11:26.840 --> 00:11:30.159
199
00:11:30.559 --> 00:11:34.000
200
00:11:34.080 --> 00:11:37.840
201
00:11:37.840 --> 00:11:40.639
202
00:11:40.679 --> 00:11:45.519
203
00:11:45.679 --> 00:11:48.639
204
00:11:48.679 --> 00:11:52.080
205
00:11:52.080 --> 00:11:55.679
206
00:11:55.759 --> 00:12:00.080
207
00:12:00.399 --> 00:12:03.519
208
00:12:03.559 --> 00:12:05.559
209
00:12:05.600 --> 00:12:08.559
210
00:12:08.559 --> 00:12:11.159
211
00:12:11.159 --> 00:12:13.559
212
00:12:13.679 --> 00:12:16.080
213
00:12:16.120 --> 00:12:18.600
214
00:12:19.200 --> 00:12:21.519
215
00:12:21.600 --> 00:12:24.759
216
00:12:24.799 --> 00:12:29.600
217
00:12:30.039 --> 00:12:33.440
218
00:12:33.519 --> 00:12:37.480
219
00:12:37.519 --> 00:12:40.440
220
00:12:40.519 --> 00:12:43.919
221
00:12:43.960 --> 00:12:46.879
222
00:12:46.919 --> 00:12:49.480
223
00:12:49.519 --> 00:12:51.799
224
00:12:51.840 --> 00:12:55.399
225
00:12:55.480 --> 00:12:59.360
226
00:12:59.399 --> 00:13:02.080
227
00:13:02.080 --> 00:13:04.919
228
00:13:05.399 --> 00:13:08.840
229
00:13:09.080 --> 00:13:12.559
230
00:13:12.960 --> 00:13:15.879
231
00:13:16.360 --> 00:13:19.799
232
00:13:19.799 --> 00:13:22.120
233
00:13:22.600 --> 00:13:24.639
234
00:13:24.799 --> 00:13:27.759
235
00:13:27.840 --> 00:13:31.480
236
00:13:31.519 --> 00:13:34.960
237
00:13:35.039 --> 00:13:37.759
238
00:13:37.840 --> 00:13:42.840
239
00:13:43.000 --> 00:13:46.960
240
00:13:47.279 --> 00:13:50.519
241
00:13:50.559 --> 00:13:53.799
242
00:13:53.879 --> 00:13:57.440
243
00:13:57.519 --> 00:14:00.240













