All skills

Generate CI/CD configuration for automated builds, tests, and distribution of iOS/macOS apps. Use when setting up GitHub Actions, Xcode Cloud, or fastlane for continuous integration, TestFlight, or App Store deployment.

Requires /macos

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/ci-cd-setup

This session only. Nothing lands on disk.

ci-cd-patterns.md

≈2.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

CI/CD Patterns

Best practices for continuous integration and deployment of iOS/macOS apps.

GitHub Actions

Runner Selection

# macOS runners for Xcode builds — one Xcode major per macOS image
runs-on: macos-15  # Apple silicon, carries Xcode 16
runs-on: macos-26  # carries Xcode 26

# Available Xcode versions (check runner images for current list)
# https://github.com/actions/runner-images/blob/main/images/macos

Xcode Version Selection

- name: Select Xcode
  run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer

# Or use xcodes action for flexibility
- uses: maxim-lobanov/setup-xcode@v1
  with:
    xcode-version: '16.2'

Caching Strategies

# Swift Package Manager cache
- name: Cache SPM
  uses: actions/cache@v4
  with:
    path: |
      .build
      ~/Library/Caches/org.swift.swiftpm
    key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
    restore-keys: |
      ${{ runner.os }}-spm-

# DerivedData cache (use with caution - can cause stale builds)
- name: Cache DerivedData
  uses: actions/cache@v4
  with:
    path: ~/Library/Developer/Xcode/DerivedData
    key: ${{ runner.os }}-derived-${{ hashFiles('**/*.xcodeproj/project.pbxproj') }}

Code Signing

# Import certificate and provisioning profile
- name: Install Certificates
  env:
    CERTIFICATE_P12: ${{ secrets.CERTIFICATE_P12 }}
    CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
    PROVISIONING_PROFILE: ${{ secrets.PROVISIONING_PROFILE }}
  run: |
    # Create temporary keychain
    KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
    KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

    security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
    security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
    security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

    # Import certificate
    echo "$CERTIFICATE_P12" | base64 --decode > $RUNNER_TEMP/certificate.p12
    security import $RUNNER_TEMP/certificate.p12 \
      -P "$CERTIFICATE_PASSWORD" \
      -A -t cert -f pkcs12 \
      -k $KEYCHAIN_PATH

    security set-key-partition-list -S apple-tool:,apple: \
      -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
    security list-keychain -d user -s $KEYCHAIN_PATH

    # Install provisioning profile
    mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
    echo "$PROVISIONING_PROFILE" | base64 --decode \
      > ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision

- name: Cleanup Keychain
  if: always()
  run: |
    security delete-keychain $RUNNER_TEMP/signing.keychain-db || true

App Store Connect API

- name: Upload to TestFlight
  env:
    API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
    API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
    API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
  run: |
    # Create API key file
    mkdir -p ~/.private_keys
    echo "$API_KEY_CONTENT" > ~/.private_keys/AuthKey_$API_KEY_ID.p8

    # Upload using xcrun altool or notarytool
    xcrun altool --upload-app \
      --type ios \
      --file "$IPA_PATH" \
      --apiKey "$API_KEY_ID" \
      --apiIssuer "$API_ISSUER_ID"

Xcode Cloud

Workflow Configuration

Xcode Cloud workflows are configured in App Store Connect or Xcode, but ci_scripts allow customization.

ci_scripts Lifecycle

1. ci_post_clone.sh    - After repository clone
2. ci_pre_xcodebuild.sh - Before xcodebuild runs
3. ci_post_xcodebuild.sh - After successful build

Environment Variables

# Available in ci_scripts
$CI                    # Always "TRUE" in Xcode Cloud
$CI_WORKSPACE          # Path to workspace
$CI_PRODUCT            # Product name
$CI_XCODE_PROJECT      # Project path
$CI_XCODE_SCHEME       # Scheme name
$CI_BRANCH             # Git branch
$CI_TAG                # Git tag (if triggered by tag)
$CI_COMMIT             # Commit SHA
$CI_BUILD_NUMBER       # Xcode Cloud build number
$CI_BUNDLE_ID          # Bundle identifier
$CI_TEAM_ID            # Apple Developer Team ID

Custom Environment Variables

Set in App Store Connect > Xcode Cloud > Workflow > Environment:

  • API_BASE_URL - Environment-specific URLs
  • FEATURE_FLAGS - Build-time feature toggles
  • SENTRY_DSN - Error monitoring (as secret)

fastlane

Lane Organization

# Fastfile structure
default_platform(:ios)

platform :ios do
  # Shared setup
  before_all do
    setup_ci if is_ci
  end

  # Development
  lane :test do
    run_tests(scheme: "MyApp")
  end

  # Beta distribution
  lane :beta do
    increment_build_number
    build_app(scheme: "MyApp")
    upload_to_testflight
  end

  # Production release
  lane :release do
    increment_build_number
    build_app(scheme: "MyApp")
    upload_to_app_store(
      submit_for_review: true,
      automatic_release: false
    )
  end

  # Error handling
  error do |lane, exception|
    # Notify on failure (Slack, etc.)
  end
end

Code Signing with match

# Matchfile
git_url("git@github.com:yourorg/certificates.git")
storage_mode("git")
type("appstore")  # development, adhoc, appstore
app_identifier(["com.yourcompany.app"])
username("your@email.com")

# In Fastfile
lane :sync_certificates do
  match(type: "development")
  match(type: "appstore")
end

Build Actions

# Build for testing
lane :build_for_testing do
  build_app(
    scheme: "MyApp",
    configuration: "Debug",
    build_for_testing: true,
    derived_data_path: "build/DerivedData"
  )
end

# Build for release
lane :build_release do
  build_app(
    scheme: "MyApp",
    configuration: "Release",
    export_method: "app-store",
    output_directory: "build",
    output_name: "MyApp.ipa"
  )
end

Versioning

# Increment version
lane :bump_version do |options|
  increment_version_number(
    bump_type: options[:type] || "patch"  # major, minor, patch
  )
end

# Increment build number
lane :bump_build do
  increment_build_number(
    build_number: latest_testflight_build_number + 1
  )
end

macOS Notarization

Using notarytool

# Submit for notarization
xcrun notarytool submit MyApp.dmg \
  --apple-id "your@email.com" \
  --team-id "TEAM_ID" \
  --password "$APP_SPECIFIC_PASSWORD" \
  --wait

# Staple the notarization ticket
xcrun stapler staple MyApp.dmg

In GitHub Actions

- name: Notarize App
  env:
    APPLE_ID: ${{ secrets.APPLE_ID }}
    TEAM_ID: ${{ secrets.TEAM_ID }}
    APP_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
  run: |
    xcrun notarytool submit build/MyApp.dmg \
      --apple-id "$APPLE_ID" \
      --team-id "$TEAM_ID" \
      --password "$APP_PASSWORD" \
      --wait

    xcrun stapler staple build/MyApp.dmg

With fastlane

lane :notarize do
  notarize(
    package: "build/MyApp.dmg",
    bundle_id: "com.yourcompany.app",
    username: ENV["APPLE_ID"],
    asc_provider: ENV["TEAM_ID"]
  )
end

Testing Strategies

Unit Tests

- name: Run Unit Tests
  run: |
    xcodebuild test \
      -scheme MyApp \
      -destination 'platform=iOS Simulator,name=iPhone 16' \
      -resultBundlePath TestResults.xcresult \
      | xcbeautify

UI Tests

- name: Run UI Tests
  run: |
    xcodebuild test \
      -scheme MyAppUITests \
      -destination 'platform=iOS Simulator,name=iPhone 16' \
      -testPlan UITests \
      | xcbeautify

Parallel Testing

- name: Run Tests in Parallel
  run: |
    xcodebuild test \
      -scheme MyApp \
      -destination 'platform=iOS Simulator,name=iPhone 16' \
      -parallel-testing-enabled YES \
      -parallel-testing-worker-count 4

Build Matrix

Multiple Platform Builds

strategy:
  matrix:
    include:
      - platform: iOS
        destination: 'platform=iOS Simulator,name=iPhone 16'
      - platform: macOS
        destination: 'platform=macOS'
      - platform: watchOS
        destination: 'platform=watchOS Simulator,name=Apple Watch Series 10'

steps:
  - name: Build for ${{ matrix.platform }}
    run: |
      xcodebuild build \
        -scheme MyApp \
        -destination '${{ matrix.destination }}'

Multiple Xcode Versions

strategy:
  matrix:
    xcode: ['15.4', '16.0', '16.2']

steps:
  - uses: maxim-lobanov/setup-xcode@v1
    with:
      xcode-version: ${{ matrix.xcode }}

Workflow Triggers

Branch-Based

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

Tag-Based Releases

on:
  push:
    tags:
      - 'v*.*.*'

Manual Triggers

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

Notifications

Slack Integration

- name: Notify Slack
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "Build failed for ${{ github.repository }}",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Build Failed* :x:\n*Repo:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}"
            }
          }
        ]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

GitHub Status Checks

- name: Update Commit Status
  uses: actions/github-script@v7
  with:
    script: |
      github.rest.repos.createCommitStatus({
        owner: context.repo.owner,
        repo: context.repo.repo,
        sha: context.sha,
        state: 'success',
        description: 'Build passed',
        context: 'CI/Build'
      })

Source: SKILL.md on GitHub

No alerts2mo3 checks · Risk SAFE
  • Gen Agent Trust Hub2mo

    This skill provides standard templates and instructions for setting up CI/CD pipelines for iOS and macOS applications using GitHub Actions, Xcode Cloud, and fastlane. It follows industry security best practices by utilizing repository secrets for sensitive data and relying on official tooling.

  • Socket2mo

    No alerts

  • Snyk2mo

    Risk: LOW · No issues

Signed by skilld at 32566aa. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 3 months ago
What it can do
Reads files Edits files Runs commands
last_verified
2026-07-16
review_by
2027-06-22
All 7 allowed tools
ReadWriteEditGlobGrepBashAskUserQuestion

README badge

README badge for rshankras/claude-code-apple-skills/ci-cd-setup