Skip to content

A preview environment per pull request

The payoff of branch environments is that reviewers click a URL and see the change running instead of reading a diff. This guide wires that into CI so it happens on every pull request automatically: push a branch, a preview provisions, its URL lands on the PR, and the environment is torn down when the PR closes.

The provisioning itself is just git push managed — that part is shipped today. The CI glue that posts URLs and gates merges is built on the public API and webhooks.

  1. A contributor pushes a branch and opens a PR.
  2. CI pushes the same commit to managed.dev, which provisions an isolated preview environment seeded from production.
  3. CI posts the preview URL back to the PR as a comment and a status check.
  4. Reviewers and QA work against the live URL; you can gate the merge on observability before promoting.
  5. When the PR closes or merges, CI tears the environment down.

A preview is created whenever you push a non-default branch that isn’t routed to staging or ignore — see branch routes. In CI, add managed.dev as a remote and push the PR’s head commit:

Provision a preview in CI
git remote add managed "$MANAGED_GIT_URL"
git push managed "HEAD:$BRANCH_NAME"

The push returns once the deploy job is queued. To get the resolved preview URL (rather than constructing it), read the environment back through the API.

Use a service token (mfs_live_…) minted with a narrow scope set — the Deploy bot preset is the right shape: sites:read deployments:write environments:write domains:read jobs:read observability:read. Look up the environment created for the branch and grab its URL:

Find the preview environment for a branch
curl -s https://api.managed.dev/v1/sites/site_01J7ABCXYZ/environments \
-H "Authorization: Bearer $MANAGED_TOKEN" \
-G --data-urlencode "branch=$BRANCH_NAME"

Then post that URL as a PR comment and a commit status. A GitHub Action that does the whole loop is forthcoming as a published, versioned action; until then, the illustrative workflow below shows the shape:

.github/workflows/managed-preview.yml
name: managed.dev preview
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
preview:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Push to managed.dev
run: |
git remote add managed "${{ secrets.MANAGED_GIT_URL }}"
git push managed "HEAD:${{ github.head_ref }}"
- name: Comment preview URL
env:
MANAGED_TOKEN: ${{ secrets.MANAGED_TOKEN }}
run: ./scripts/post-preview-url.sh

Because previews carry the full observability pipeline, you can make “no new errors” a merge requirement instead of a vibe. Query the preview’s traces for error-status rows and fail the check if any appear:

Block promotion when the preview is throwing errors
errors=$(curl -s "https://api.managed.dev/v1/environments/$ENV_ID/observability/traces?status=error&limit=1" \
-H "Authorization: Bearer $MANAGED_TOKEN" | jq '.data | length')
test "$errors" -eq 0 || { echo "preview has error traces — blocking merge"; exit 1; }

This is the deploy-gated-on-observability pattern: you only promote a branch that’s demonstrably clean on its own preview.

When the PR closes, delete the environment so it doesn’t linger. Deleting the branch also tears the preview down automatically, but an explicit call is cleaner in CI:

Tear down on PR close
curl -X DELETE "https://api.managed.dev/v1/sites/$SITE_ID/environments/$ENV_ID" \
-H "Authorization: Bearer $MANAGED_TOKEN" \
-H "Idempotency-Key: pr-${PR_NUMBER}-teardown"

The Idempotency-Key makes a retried teardown safe — a replay returns the original result instead of erroring. Production and staging are never removed this way; only previews are ephemeral.