Guía - Automatización

Fastlane + Servidor Mac dedicado: Guía completa de automatización

Automatice todo su proceso de publicación iOS -- compilaciones, firma de código, capturas de pantalla y despliegue en TestFlight -- usando Fastlane en un servidor Mac Mini M4 dedicado.

35 min de lectura Actualizado en enero 2025

1. ¿Por qué Fastlane en un Mac dedicado?

Fastlane es la herramienta de automatización estándar para desarrollo iOS y Android. Maneja todo, desde la firma de código hasta el despliegue en TestFlight. Ejecutar Fastlane en un Mac Mini M4 dedicado le ofrece:

Keychain persistente

Los certificados de firma de código persisten entre ejecuciones. No es necesario importar/exportar en cada compilación.

Compilaciones incrementales rápidas

DerivedData persiste, por lo que fastlane build tarda 2-4 minutos en lugar de más de 15.

Capturas de pantalla del simulador

Ejecute fastlane snapshot con simuladores reales para todos los tamaños de dispositivo.

Despliegues fiables

Un entorno estable y dedicado significa menos despliegues fallidos y problemas de firma de código.

2. Instalar Fastlane

Conéctese por SSH a su Mac Mini M4 e instale Fastlane. Recomendamos Homebrew para la configuración más simple:

Opción A: Instalar vía Homebrew (Recomendado)

# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

# Install Fastlane
brew install fastlane

# Verify installation
fastlane --version
# fastlane 2.225.0

Opción B: Instalar vía RubyGems

# Use the system Ruby or install rbenv for version management
gem install fastlane -NV

# Or with Bundler (recommended for team consistency):
# Create a Gemfile in your project root
cat > Gemfile <<'EOF'
source "https://rubygems.org"

gem "fastlane"
gem "cocoapods"  # if using CocoaPods
EOF

bundle install

Inicializar Fastlane en su proyecto

# Navigate to your project directory
cd /path/to/your/ios-project

# Initialize Fastlane
fastlane init

# Choose option 4: "Manual setup"
# This creates the fastlane/ directory with Appfile and Fastfile

3. Configurar Match para la firma de código

Fastlane Match almacena sus certificados de firma de código y perfiles de aprovisionamiento en un repositorio Git privado o almacenamiento en la nube. Esto asegura que todas las máquinas (y miembros del equipo) usen la misma identidad de firma.

Inicializar Match

# Initialize match (choose "git" for storage)
fastlane match init

# This creates fastlane/Matchfile

Configurar Matchfile

# fastlane/Matchfile

git_url("https://github.com/your-org/ios-certificates.git")

storage_mode("git")

type("appstore")  # default type, can be overridden per lane

app_identifier(["com.yourcompany.myapp"])
username("your-apple-id@example.com")

# For CI environments, use App Store Connect API key instead of username/password
# api_key_path("fastlane/AuthKey.json")

Generar certificados

# Generate development certificates and profiles
fastlane match development

# Generate App Store distribution certificates and profiles
fastlane match appstore

# For ad-hoc distribution
fastlane match adhoc

# On CI, use readonly mode to avoid accidentally creating new certs
fastlane match appstore --readonly

4. Crear Fastfile (Lanes de compilación, pruebas y despliegue)

Aquí tiene un Fastfile completo con lanes para compilar, probar y desplegar su app iOS:

# fastlane/Fastfile

default_platform(:ios)

platform :ios do

  # ---- SHARED ----

  before_all do
    setup_ci if ENV['CI']  # Configures keychain for CI environments
  end

  # ---- BUILD ----

  desc "Build the app for testing"
  lane :build do
    match(type: "development", readonly: true)

    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      configuration: "Debug",
      destination: "generic/platform=iOS Simulator",
      derived_data_path: "DerivedData",
      skip_archive: true,
      skip_codesigning: true
    )
  end

  # ---- TEST ----

  desc "Run all unit and UI tests"
  lane :test do
    run_tests(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      devices: ["iPhone 16 Pro"],
      derived_data_path: "DerivedData",
      result_bundle: true,
      output_directory: "fastlane/test_results",
      parallel_testing: true,
      concurrent_workers: 4
    )
  end

  # ---- BETA ----

  desc "Build and push a new beta to TestFlight"
  lane :beta do
    # Ensure we are on a clean git state
    ensure_git_status_clean

    # Fetch App Store certificates
    match(type: "appstore", readonly: true)

    # Increment build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )

    # Build the app
    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      export_method: "app-store",
      derived_data_path: "DerivedData",
      output_directory: "fastlane/builds"
    )

    # Upload to TestFlight
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
      api_key_path: "fastlane/AuthKey.json"
    )

    # Commit the version bump
    commit_version_bump(
      message: "chore: bump build number [skip ci]",
      force: true
    )

    # Tag the release
    add_git_tag(
      tag: "beta/#{lane_context[SharedValues::BUILD_NUMBER]}"
    )

    push_to_git_remote
  end

  # ---- RELEASE ----

  desc "Build and submit to App Store Review"
  lane :release do
    match(type: "appstore", readonly: true)

    # Increment version number (patch)
    increment_version_number(bump_type: "patch")
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )

    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      export_method: "app-store",
      derived_data_path: "DerivedData"
    )

    upload_to_app_store(
      submit_for_review: true,
      automatic_release: false,
      api_key_path: "fastlane/AuthKey.json",
      precheck_include_in_app_purchases: false
    )

    commit_version_bump(message: "chore: release #{lane_context[SharedValues::VERSION_NUMBER]}")
    add_git_tag
    push_to_git_remote
  end

  # ---- ERROR HANDLING ----

  error do |lane, exception|
    # Send notification on failure (Slack, email, etc.)
    # slack(
    #   message: "Lane #{lane} failed: #{exception.message}",
    #   success: false
    # )
  end
end

5. Configurar capturas de pantalla automatizadas

Fastlane Snapshot captura capturas de pantalla del App Store en múltiples dispositivos e idiomas automáticamente. En un Mac dedicado, esto se ejecuta de forma fiable sin competir por recursos.

# Initialize snapshot
fastlane snapshot init

# This creates:
# - fastlane/Snapfile
# - fastlane/SnapshotHelper.swift (add to UI test target)

Configurar Snapfile

# fastlane/Snapfile

devices([
  "iPhone 16 Pro Max",
  "iPhone 16 Pro",
  "iPhone SE (3rd generation)",
  "iPad Pro 13-inch (M4)"
])

languages([
  "en-US",
  "fr-FR",
  "de-DE",
  "ja"
])

scheme("MyAppUITests")
output_directory("./fastlane/screenshots")
clear_previous_screenshots(true)

# Speed up by running in parallel
concurrent_simulators(true)

Añadir Snapshot a sus pruebas de interfaz

// In your XCUITest file:
import XCTest

class ScreenshotTests: XCTestCase {

    override func setUp() {
        continueAfterFailure = false
        let app = XCUIApplication()
        setupSnapshot(app)
        app.launch()
    }

    func testHomeScreen() {
        snapshot("01_HomeScreen")
    }

    func testDetailScreen() {
        let app = XCUIApplication()
        app.cells.firstMatch.tap()
        snapshot("02_DetailScreen")
    }

    func testSettings() {
        let app = XCUIApplication()
        app.tabBars.buttons["Settings"].tap()
        snapshot("03_Settings")
    }
}

Añadir un lane de capturas de pantalla

  # Add to your Fastfile:
  desc "Capture App Store screenshots"
  lane :screenshots do
    capture_screenshots
    frame_screenshots(white: true)  # Add device frames
    upload_to_app_store(
      skip_binary_upload: true,
      skip_metadata: true,
      api_key_path: "fastlane/AuthKey.json"
    )
  end

6. Desplegar en TestFlight

Para entornos CI, use la clave API de App Store Connect en lugar de las credenciales de su Apple ID. Esto evita las solicitudes de 2FA en servidores headless.

Crear una clave API

  1. Vaya a App Store Connect > Usuarios y acceso > Claves
  2. Haga clic en el botón + para generar una nueva clave API
  3. Seleccione el rol App Manager
  4. Descargue el archivo .p8
  5. Anote el Key ID y el Issuer ID

Crear el JSON de la clave API

# fastlane/AuthKey.json
{
  "key_id": "YOUR_KEY_ID",
  "issuer_id": "YOUR_ISSUER_ID",
  "key": "-----BEGIN PRIVATE KEY-----\nYOUR_P8_KEY_CONTENT\n-----END PRIVATE KEY-----",
  "in_house": false
}

# IMPORTANT: Add this to .gitignore!
echo "fastlane/AuthKey.json" >> .gitignore

Ejecutar el despliegue

# Deploy to TestFlight
fastlane beta

# Or run the full release pipeline
fastlane release

7. Integrar con CI/CD

Fastlane se integra sin problemas con cualquier plataforma CI/CD. Aquí tiene un ejemplo de GitHub Actions usando su Mac Mini M4 self-hosted runner:

# .github/workflows/deploy.yml
name: Deploy to TestFlight

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: [self-hosted, macOS, ARM64, M4]

    env:
      MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
      MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_TOKEN }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up App Store Connect API Key
        run: |
          mkdir -p fastlane
          echo '${{ secrets.APP_STORE_CONNECT_API_KEY }}' > fastlane/AuthKey.json

      - name: Install dependencies
        run: |
          bundle install
          pod install  # if using CocoaPods

      - name: Deploy to TestFlight
        run: bundle exec fastlane beta

      - name: Clean up API key
        if: always()
        run: rm -f fastlane/AuthKey.json

8. Mejores prácticas

Usar Bundler para fijar la versión de Fastlane

Añada un Gemfile con una versión fija de Fastlane. Ejecute bundle exec fastlane para asegurar versiones consistentes en todos los entornos.

Usar claves API de App Store Connect, no credenciales de Apple ID

Las claves API evitan las solicitudes de 2FA y son más seguras para entornos CI.

Usar match --readonly en CI

Previene la creación accidental de nuevos certificados. Solo genere certificados manualmente cuando sea necesario.

Usar setup_ci en entornos CI

Esto crea un keychain temporal para evitar contaminar el keychain del sistema y previene los diálogos de permisos del keychain.

Mantener DerivedData para compilaciones más rápidas

En un Mac dedicado, especifique derived_data_path para reutilizar artefactos de compilación entre ejecuciones.

Guías relacionadas

Automatice sus publicaciones iOS hoy

Obtenga un Mac Mini M4 dedicado y ejecute Fastlane con compilaciones ilimitadas. Desde $75/mes con una prueba gratuita de 7 días.