Once you've got the basic patch flow working and you understand when to patch vs ship, the next step is wiring it into a real workflow so you're not running CLI commands from your laptop at midnight when production is on fire.

This is the production setup I run on my own apps. Three pieces: CI for releases, staged rollouts for safety, and a few small habits that catch problems before they hit users.

Roll a patch to a small slice of users first. If nothing breaks, promote to everyone. The hour in between is the cheapest insurance you can buy.

Step 1: GitHub Actions for releases

The biggest mistake I see is forgetting which builds were created with shorebird release versus plain flutter build. Only the former are patchable. The fix is to never run flutter build for store builds — always go through Shorebird, and automate it from CI so you can't forget.

Here's the workflow I use:

.github/workflows/release.ymlyaml
name: Release

on:
push:
  tags:
    - 'v*'

jobs:
release-android:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - uses: subosito/flutter-action@v2
      with:
        channel: stable

    - uses: shorebirdtech/setup-shorebird@v1

    - name: Authenticate Shorebird
      env:
        SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
      run: echo "$SHOREBIRD_TOKEN" | shorebird login:ci

    - name: Decode keystore
      env:
        KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
      run: |
        echo "$KEYSTORE_BASE64" | base64 -d > android/upload-keystore.jks

    - name: Create release
      run: shorebird release android --no-confirm

    - name: Upload AAB
      uses: actions/upload-artifact@v4
      with:
        name: app-release.aab
        path: build/app/outputs/bundle/release/*.aab

The flow: tag a commit with v1.2.3, push the tag, GitHub Actions runs shorebird release android, the resulting AAB is uploaded as a build artifact for you to drop into Google Play Console. Same idea for iOS, with the added joy of code signing on a macOS runner.

Step 2: Patches from CI too

Once releases are automated, do the same for patches. I gate this on a separate tag prefix so I never accidentally patch when I meant to release:

.github/workflows/patch.ymlyaml
name: Patch

on:
push:
  tags:
    - 'patch-*'

jobs:
patch-android:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: subosito/flutter-action@v2
      with:
        channel: stable
    - uses: shorebirdtech/setup-shorebird@v1

    - name: Authenticate
      env:
        SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
      run: echo "$SHOREBIRD_TOKEN" | shorebird login:ci

    - name: Patch latest release
      run: shorebird patch android --no-confirm

Now my emergency flow looks like:

  1. Fix the bug locally
  2. Open a PR, get a review (yes, even for hotfixes)
  3. Merge, then git tag patch-2026-04-10-fix-crash && git push --tags
  4. CI patches Android and iOS
  5. Watch the dashboard

The PR review step is the biggest safety upgrade. It costs 5 minutes and catches the "fix that breaks more than it fixes" bug at least once a year for me.

Step 3: Staged rollouts

Shorebird supports staged rollouts: roll a patch out to a percentage of users, watch for issues, then roll it to 100%. This is the most important production feature and the one most people skip until they get burned.

bash
# Stage to 5% of users on the latest release
shorebird patch android --staging --percentage 5

# After confirming things look good, promote to 100%
shorebird patches promote --patch-number 3

My rule: any patch that touches a code path with more than ~10% of usage gets staged at 5% first. Trivial copy fixes can go straight to 100%. Anything that touches auth, payments, sync, or data persistence rolls to 5%, sits for an hour, then promotes.

Step 4: Tracking patch adoption

Shorebird's dashboard shows you real-time install counts per patch. The metrics I check:

  • Adoption rate — what percentage of active users on the release have downloaded this patch? Climbs over the first 24 hours, plateaus around 70-80%, then keeps creeping up as users open the app.
  • Crash rate per patch — your crash reporter (Sentry, Crashlytics, whatever you use) should let you tag events with the patch number. Compare patch N's crash rate against patch N-1.
  • Revert rate — how many times have you had to delete this patch? If it's nonzero, you're not reviewing patches carefully enough.

I check the dashboard about an hour after pushing a patch, and again the next morning. That's it.

A safety net I always add

Wrap your Shorebird init in a try/catch so a Shorebird outage can never break your app launch:

lib/main.dartdart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();

try {
  // Shorebird auto-initializes on launch — but if its servers are
  // unreachable or the device is offline, we don't want that to
  // block the app from starting.
  await Future.any([
    // Whatever your Shorebird init looks like
    Future.delayed(Duration.zero),
    Future.delayed(const Duration(seconds: 2)),
  ]);
} catch (_) {
  // Swallow — the app will start without the patch.
  // The patch will apply on the next launch.
}

runApp(const MyApp());
}

In practice the Shorebird SDK already handles this gracefully — but I sleep better knowing I have my own timeout in front of it.

What I skip

A few things people set up that I don't bother with:

  • Per-environment Shorebird projects — I have one Shorebird app ID per Flutter app, and I rely on tags + manual environment selection. Multiple flavors complicate the patch logic enough that I find it cleaner to just run the CLI manually for non-production environments.
  • Patch tests in CI — Shorebird patches don't really lend themselves to automated testing because they only apply to installed builds. Manual smoke test on a spare device is faster.
  • Custom rollout percentages beyond 5/100 — I tried 10/50/100 once and it was just more work. 5% staged → 100% is the entire ladder I need.

The full production checklist

Before any app I ship goes live with Shorebird:

  1. shorebird init committed to repo
  2. release.yml and patch.yml workflows in .github/workflows/
  3. SHOREBIRD_TOKEN in GitHub secrets
  4. Crash reporter (Sentry or similar) tagged with the current patch number
  5. A test release pushed to internal track to verify the CI flow
  6. Documented in the team README: "tag with v* to release, tag with patch-* to patch, always stage at 5% first"

Six steps. After that, patching is muscle memory and you can sleep at night.