Skip to content
CloudOps
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 11 min read

Power Automate ALM: Ship Teams Flows Across Environments Safely

Hand-built flows in production are a liability. Here's solution-based ALM for Power Automate: environments, managed solutions, connection references, and pipelines.

  • #microsoft-teams
  • #power-automate
  • #alm
  • #power-platform
  • #ci-cd

The first time I broke a Teams approval flow in production, I did it the same way most people do: I opened the flow in the maker portal, clicked “Edit,” tweaked a condition, and saved. No branch, no review, no rollback. Five minutes later a manager pinged me asking why every leave request was now auto-approving. I had become the change management process, and I was a single point of failure with a mouse.

That afternoon convinced me that Power Automate is not a no-code toy you can wing in production. It is software. And software that drives Teams notifications, SharePoint writes, and approval routing deserves the same discipline as anything in your CI/CD pipeline. The good news: Power Platform has had a real application lifecycle management (ALM) story for years now. Most teams just never turn it on. Let me walk you through the setup I wish I’d had on day one.

Stop Editing in Prod: The Environment Model

The foundation of Power Platform ALM is environments. An environment is an isolated container with its own Dataverse instance, its own users, and its own connections. The canonical setup is three of them: Dev, Test, and Prod.

  • Dev is where makers build. It has Dataverse provisioned because solutions need it.
  • Test (or UAT) is where you validate a deployment before it touches users.
  • Prod is locked down. Makers do not build here. Ideally they cannot even create flows here directly — you set the environment to a managed posture so the only way in is through a deployment.

The “edit in prod” trap happens because the maker portal makes prod feel exactly like dev. The fix is policy plus muscle memory: prod is read-only to humans. Changes arrive as deployment artifacts, never as live clicks. If you only adopt one idea from this post, make it this one.

Solutions: Unmanaged in Dev, Managed Everywhere Else

A solution is the unit of packaging in Power Platform — think of it as the .deb or container image for your flows, tables, connection references, and environment variables. Everything you want to ship lives inside a solution. Loose, “default solution” flows cannot be cleanly moved, which is exactly how people end up rebuilding flows by hand in each environment.

There are two flavors:

  • Unmanaged solutions are editable. This is your source. You build in unmanaged solutions in Dev only.
  • Managed solutions are sealed. Components can’t be edited in place, and deleting the managed solution cleanly removes everything it added. This is what you deploy to Test and Prod.

The rule is simple: author unmanaged in Dev, export as managed, import managed downstream. Never import an unmanaged solution into Test or Prod — you lose the ability to layer and uninstall cleanly, and you reopen the door to in-place edits.

Connection References and Environment Variables

Here’s where most “I copied my flow and it broke” stories come from. A flow in Dev points at your Dev SharePoint site, your Dev Teams channel, your Dev SQL connection. If you hardcode those, moving the flow to Prod means it still talks to Dev.

Two abstractions solve this:

Connection references decouple a flow from a specific connection. The flow says “use the connection reference named Shared_SharePoint_Prod,” and at import time you bind that reference to the actual connection in the target environment. Same flow definition, different wiring per environment.

Environment variables do the same for values — site URLs, channel IDs, API endpoints, feature flags. Instead of pasting https://contoso.sharepoint.com/sites/hr-dev into a flow action, you reference an environment variable and set its value per environment at deploy time.

A schema-only environment variable definition inside your unpacked solution looks like this:

<environmentvariabledefinition schemaname="contoso_HrSiteUrl">
  <displayname>HR Site URL</displayname>
  <type>String</type>
  <isrequired>1</isrequired>
  <!-- No <defaultvalue> baked in: the value is supplied per environment -->
</environmentvariabledefinition>

Notice there is no value committed. The definition travels in the managed solution; the value is set in Test and Prod during deployment. That separation is the whole game — your artifact is environment-agnostic, and configuration is injected late.

Pro Tip: Treat connection references and environment variables like 12-factor config. If a value differs between Dev and Prod, it is configuration, not code, and it does not belong inside the flow definition. Audit any flow for hardcoded URLs and channel IDs before you ever export it.

Source-Controlling the Solution XML

A solution exported as a .zip is opaque — you can’t diff it, you can’t review it in a pull request, and Git treats it as a binary blob. The Power Platform CLI (pac) fixes this with pack/unpack. Unpacking explodes the zip into a folder tree of human-readable XML and JSON, one file per component. That tree is what you commit.

Now a flow change shows up as a readable diff. A reviewer can see that someone changed an approval condition or added a Delete File action, and a security reviewer can confirm which connectors a flow touches before it ever ships. This is the moment Power Automate stops being shadow IT and starts being reviewable software.

A Real pac CLI Sequence

Here is the round-trip I run, from authenticating against Dev to producing a committable, packable artifact:

# Authenticate against the Dev environment
pac auth create --name dev --url https://contoso-dev.crm.dynamics.com

# Export the unmanaged solution from Dev (your source of truth)
pac solution export \
  --path ./out/HrFlows_unmanaged.zip \
  --name HrFlows \
  --managed false

# Also export a managed copy for downstream deployment
pac solution export \
  --path ./out/HrFlows_managed.zip \
  --name HrFlows \
  --managed true

# Unpack the unmanaged solution into source-controllable XML
pac solution unpack \
  --zipfile ./out/HrFlows_unmanaged.zip \
  --folder ./src/HrFlows \
  --packagetype Unmanaged

# ... commit ./src/HrFlows, open a PR, get it reviewed ...

# In CI: repack from source and import the managed build into Test
pac solution pack \
  --zipfile ./build/HrFlows_managed.zip \
  --folder ./src/HrFlows \
  --packagetype Managed

pac auth create --name test --url https://contoso-test.crm.dynamics.com
pac solution import \
  --path ./build/HrFlows_managed.zip \
  --force-overwrite \
  --publish-changes

In a pipeline you pass --settings-file to pac solution import (or use a deployment settings JSON) to bind connection references and set environment variable values for the target environment. The same artifact flows Dev → Test → Prod; only the settings file changes.

Pipelines vs. Build Tools: Pick Your Lane

You have two delivery mechanisms, and they suit different teams.

Power Platform Pipelines are the in-product option. You configure source and target environments in a dedicated pipelines host environment, and makers deploy with a button click — approvals optional. It is low-friction and great for teams that live inside the Power Platform admin center and don’t want to own a YAML pipeline. The trade-off is less control and no native Git step.

Power Platform Build Tools are the pro-dev option: a set of tasks for Azure DevOps and equivalent GitHub Actions that wrap the same pac commands. This is where you get true CI/CD — pull request gates, the unpacked solution in Git, automated managed builds, and gated promotion to Prod. A minimal GitHub Actions step looks like:

- name: Import managed solution to Test
  uses: microsoft/powerplatform-actions/import-solution@v1
  with:
    environment-url: ${{ vars.TEST_ENV_URL }}
    app-id: ${{ secrets.PP_APP_ID }}
    client-secret: ${{ secrets.PP_CLIENT_SECRET }}
    tenant-id: ${{ secrets.PP_TENANT_ID }}
    solution-file: ./build/HrFlows_managed.zip
    use-deployment-settings-file: true
    deployment-settings-file: ./settings/test.json

My rule of thumb: if you already have an Azure DevOps or GitHub Actions practice, use the Build Tools and treat flows like any other deployable. If your team is citizen-developer heavy and you just need governed promotion, Pipelines will get you 80% of the discipline for 20% of the effort.

Where AI Fits (and Where It Doesn’t)

I lean on AI heavily for this work, and I think about it the way I’d think about a fast, eager junior engineer. Tools like Claude or GitHub Copilot will draft a GitHub Actions YAML pipeline, scaffold a pac command sequence, or explain an obscure solution.xml element in seconds. That speed is real and worth using — I keep reusable starting points in our prompt packs for exactly this.

But a junior engineer does not get commit access to your tenant on day one, and neither does the model. A few hard lines I hold:

  • A human reviews before anything hits a tenant. AI-drafted pipeline YAML and import settings get read line by line, especially the connection reference bindings and environment variable values.
  • Verify connector security yourself. Ask the model to list every connector a flow touches, then confirm those connectors are allowed by your DLP policy. Do not trust its summary as a security control.
  • Never hand the model real tenant credentials. No client secrets, no app registration IDs, no auth tokens in a prompt. Those live in your secret store and your pipeline, never in a chat window.

Pro Tip: Use AI to generate the deployment settings file template and the pipeline scaffolding, then fill in real values from your secret manager by hand. The model is great at structure and terrible at being trusted with secrets — play to that split.

If you want to build that review-first habit, our prompt library has reviewable, security-aware prompts for pipeline generation, and the Microsoft Teams hub collects more of these flow-governance walkthroughs.

Wrapping Up

Power Automate ALM is not exotic. It is environments, solutions, late-bound configuration, and a pipeline — the same shape as every other deployment you already run. The maker portal makes it dangerously easy to skip all of that, which is exactly why the discipline matters. Author unmanaged in Dev, ship managed everywhere else, keep your connections and URLs out of the flow definition, and put the unpacked XML under review. Let AI draft fast, let a human approve, and never let either one edit in prod.

slug: power-automate-alm-ship-teams-flows-across-environments

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.