Set up CI/CD pipelines for React Native/Expo or Flutter apps. Covers GitHub Actions workflows, EAS Build integration, build caching, code signing in CI, secrets management, PR preview builds, and conditional platform builds. Use when the user wants automated builds, tests, or deployments on push or pull request.
Use this skill when the user:
Generate a basic CI workflow. Use the MCP tool for a starter config:
The mobile_setupCI tool creates .github/workflows/ci.yml with lint, type check, and test steps. Customize it after generation.
Expo CI workflow structure. A production-grade pipeline:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Type check
run: npx tsc --noEmit
- name: Lint
run: npx expo lint
- name: Test
run: npx jest --ci --coverage
eas-build:
needs: lint-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: Build
run: eas build --platform all --profile production --non-interactive
Flutter CI workflow structure.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: flutter analyze
- run: flutter test --coverage
build-android:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: flutter build apk --release
build-ios:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: flutter build ios --release --no-codesign
Manage secrets for code signing. Store credentials as GitHub encrypted secrets:
| Secret | Purpose | How to get it |
|---|---|---|
EXPO_TOKEN | EAS CLI auth | eas credentials or expo.dev dashboard |
KEYSTORE_BASE64 | Android signing keystore | base64 -i release.keystore |
KEYSTORE_PASSWORD | Keystore password | From your key generation step |
KEY_ALIAS | Key alias | From your key generation step |
KEY_PASSWORD | Key password | From your key generation step |
For Expo/EAS projects, code signing is managed by EAS. You only need EXPO_TOKEN.
For bare React Native or Flutter, decode the keystore in CI:
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/release.keystore
Cache dependencies for faster builds. Node modules and Flutter SDK caching:
# Node (already handled by setup-node cache: npm)
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
# Flutter (already handled by flutter-action cache: true)
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
# Gradle cache for Android builds
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
# CocoaPods cache for iOS builds
- uses: actions/cache@v4
with:
path: ios/Pods
key: pods-${{ hashFiles('ios/Podfile.lock') }}
Add PR preview builds with EAS. Build a preview version on every PR:
Add a preview profile in eas.json:
{
"build": {
"preview": {
"distribution": "internal",
"channel": "preview",
"ios": { "simulator": true }
}
}
}
Add a workflow job:
preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: eas build --platform all --profile preview --non-interactive
Conditional platform builds. Only build the platform that changed:
changes:
runs-on: ubuntu-latest
outputs:
ios: ${{ steps.filter.outputs.ios }}
android: ${{ steps.filter.outputs.android }}
steps:
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
ios:
- 'ios/**'
- 'Podfile.lock'
android:
- 'android/**'
- 'build.gradle'
Deploy on merge. Submit to stores automatically when merging to main:
deploy:
needs: eas-build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: eas submit --platform all --profile production --non-interactive
User: "Set up CI for my Expo app. I want tests on every PR and a production build when I merge to main."
Agent:
mobile_setupCI with framework=expo, platforms=both, include_tests=true, include_eas_build=true.github/workflows/ci.ymlEXPO_TOKEN secret in GitHub repo settingslint-and-test job to pass| Step | MCP Tool | Description |
|---|---|---|
| Generate workflow | mobile_setupCI | Create GitHub Actions CI/CD workflow file |
| Run tests locally first | mobile_runTests | Verify tests pass before pushing to CI |
| Validate build config | mobile_checkBuildHealth | Check that app.json and dependencies are valid |
| Build for store | mobile_buildForStore | Trigger a production EAS Build locally or in CI |
| Submit to stores | mobile_submitToAppStore, mobile_submitToPlayStore | Automate store submission from CI |
EXPO_TOKEN, keystores, or API keys in workflow files. Use GitHub encrypted secrets.runs-on: macos-latest for Xcode and simulator steps. EAS Build handles this for you.npm ci and flutter pub get download everything on each run. Use the cache option in setup actions.main and pull requests. Building on feature branch pushes wastes CI minutes.@v4 not @latest for third-party actions. Unpinned versions can break without warning.npx tsc --noEmit catches type errors that developers may ignore locally. Always include it.