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:
Los certificados de firma de código persisten entre ejecuciones. No es necesario importar/exportar en cada compilación.
DerivedData persiste, por lo que fastlane build tarda 2-4 minutos en lugar de más de 15.
Ejecute fastlane snapshot con simuladores reales para todos los tamaños de dispositivo.
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
- Vaya a App Store Connect > Usuarios y acceso > Claves
- Haga clic en el botón + para generar una nueva clave API
- Seleccione el rol App Manager
- Descargue el archivo
.p8 - 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
Añada un Gemfile con una versión fija de Fastlane. Ejecute bundle exec fastlane para asegurar versiones consistentes en todos los entornos.
Las claves API evitan las solicitudes de 2FA y son más seguras para entornos CI.
match --readonly en CI
Previene la creación accidental de nuevos certificados. Solo genere certificados manualmente cuando sea necesario.
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.
En un Mac dedicado, especifique derived_data_path para reutilizar artefactos de compilación entre ejecuciones.
Guías relacionadas
GitHub Actions Self-Hosted Runner
Configure un GitHub Actions runner en su Mac Mini M4.
Agente de compilación Jenkins para Mac
Configure agentes de compilación Jenkins en un Mac Mini dedicado para CI/CD de iOS.
GitLab CI Mac Runner
Registre un GitLab Runner en un Mac Mini dedicado para pipelines CI de iOS/macOS.