Skip to content

Artifacts

Artifacts allow you to collect and preserve build outputs like .ipa, .app, and other build products. RunnerHub automatically uploads artifacts at the end of every job, regardless of whether the job succeeded or failed.

Define artifacts using glob patterns in the artifacts field:

artifacts:
- build/**/*.ipa
- build/**/*.dSYM

Artifacts are collected after all pipeline steps complete — whether the job status is SUCCESS, FAILED, or TIMEOUT. This means test reports, crash logs, and partial build outputs are preserved even when a build fails. Artifacts are not uploaded only when a job is cancelled.

Artifact uploads have the following limits:

  • Per-file limit: 2 GB per artifact file
  • Per-job limit: 5 GB total per job
  • File count limit: 20 files per job

Uploads exceeding these limits are rejected by the agent.

Artifact patterns use standard glob syntax:

| Pattern | Matches | |---------|---------| | build/**/*.ipa | All .ipa files in build directory and subdirectories | | build/*.app | All .app files directly in build directory | | **/*.dSYM | All .dSYM files anywhere in the repository | | build/output/* | All files directly in build/output | | dist/app.{ipa,app} | app.ipa or app.app in the dist directory |

Define multiple patterns to collect different types of artifacts:

name: iOS Build
platform: ios
environment:
xcode: "16.4"
triggers:
- push
steps:
- name: Build
run: fastlane build
artifacts:
- build/**/*.ipa # Release builds
- build/**/*.app # Debug builds
- build/**/*.dSYM # Debug symbols
- fastlane/report.html # Build report

All matching files are collected and uploaded.

artifacts:
- build/**/*.ipa # App archive
- build/**/*.app # Unpackaged app
- build/**/*.dSYM # Debug symbols
- build/**/*.xcarchive # Xcode archive
artifacts:
- build/**/*.app # App bundle
- build/**/*.dmg # Disk image
- build/**/*.dSYM # Debug symbols
artifacts:
- ios/build/**/*.app # iOS app (debug)
- ios/build/**/*.ipa # iOS release build
- ios/build/**/*.dSYM # iOS debug symbols
- android/app/build/outputs/**/*.apk # Android release APK
- android/app/build/outputs/bundle/**/*.aab # Android App Bundle
artifacts:
- fastlane/report.html # Fastlane build report
- build/reports/**/*.xml # Test reports
- coverage/report.html # Coverage reports

The system variable RUNNERHUB_ARTIFACTS_DIR points to a directory where you can explicitly place artifacts:

name: Build
platform: ios
environment:
xcode: "16.4"
steps:
- name: Build
run: |
fastlane build
# Optionally copy artifacts to the artifacts directory
cp build/app.ipa $RUNNERHUB_ARTIFACTS_DIR/
cp build/app.dSYM $RUNNERHUB_ARTIFACTS_DIR/

Artifacts in this directory are automatically collected, or you can use glob patterns to collect them from elsewhere in your build.

How long artifacts are stored depends on your RunnerHub plan:

| Plan | Retention | Download | Storage | |------|-----------|----------|---------| | Free | Not available | — | Artifact upload is skipped on the Free plan; no artifacts are stored. Listing artifacts: in YAML on Free has no effect. | | Pay-as-you-go (PAYG) | 7 days | Dashboard | Stored in RunnerHub | | Pro | 30 days | Dashboard | Stored in RunnerHub | | Business | 90 days | Dashboard | Stored in RunnerHub |

Free plan: Artifact upload is skipped entirely on the Free plan — no files are stored and no error is returned. The artifacts: field is silently ignored. To collect build outputs, upgrade your plan or configure your own upload in the pipeline (S3, Firebase App Distribution, TestFlight, etc.).

PAYG and above: Artifacts are stored and downloadable from the RunnerHub dashboard for the specified retention period.

  1. Open a completed job in your app
  2. Navigate to the Artifacts tab
  3. Click the download icon next to each artifact
  4. Artifacts are available for the retention period of your plan

Access artifacts programmatically in your pipeline:

name: Upload
platform: ios
environment:
xcode: "16.4"
steps:
- name: Upload to Google Cloud Storage
run: |
gsutil -m cp -r $RUNNERHUB_ARTIFACTS_DIR gs://my-bucket/builds/

Here's a full iOS pipeline that builds and collects artifacts:

name: iOS Release Build
platform: ios
environment:
xcode: "16.4"
variables:
LC_ALL: en_US.UTF-8
triggers:
- push
- pull_request
steps:
- name: Install dependencies
run: bundle install
- name: Install pods
run: pod install
- name: Run tests
run: fastlane test
- name: Build Release
run: fastlane build
- name: Copy artifacts
run: |
cp -r build/Release-iphoneos/*.ipa $RUNNERHUB_ARTIFACTS_DIR/
cp -r build/Release-iphoneos/*.dSYM $RUNNERHUB_ARTIFACTS_DIR/
artifacts:
- build/**/*.ipa
- build/**/*.dSYM
timeout: 45

Considerations:

  • Artifacts are stored on RunnerHub servers (PAYG+)
  • Large artifacts may take longer to upload
  • Free plan artifacts are cleaned up, so size is less critical
  • Consider storage limits for your plan

Optimization Tips:

# Good: Only essential artifacts
artifacts:
- build/**/*.ipa # Final release build
- build/**/*.dSYM # Debug symbols
# Avoid: Unnecessary intermediate files
artifacts:
- build/**/* # Everything (too broad)
- build/DerivedData/** # Redundant (already cached)
- build/**/*.o # Object files (not needed)

To skip artifact collection, omit the artifacts field:

name: Tests Only
platform: ios
environment:
xcode: "16.4"
triggers:
- pull_request
steps:
- name: Run tests
run: fastlane test
# No artifacts field → no collection

Use your build system's built-in features to conditionally upload artifacts:

name: Conditional Build
platform: ios
environment:
xcode: "16.4"
steps:
- name: Build and Upload
run: |
fastlane build
if [ "$RUNNERHUB_TRIGGER" = "push" ] && [ "$RUNNERHUB_BRANCH" = "main" ]; then
echo "Uploading to TestFlight..."
fastlane upload
else
echo "Debug build, artifacts only for internal review"
fi
artifacts:
- build/**/*.ipa
- build/**/*.dSYM

On the Free plan, artifacts are not retained in RunnerHub. Use this strategy:

name: iOS Build
platform: ios
environment:
xcode: "16.4"
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
triggers:
- push
steps:
- name: Build
run: fastlane build
- name: Upload to Google Cloud Storage
run: |
gsutil -m cp -r build/app.ipa gs://my-build-artifacts/$RUNNERHUB_BRANCH/
gsutil -m cp -r build/app.dSYM gs://my-build-artifacts/$RUNNERHUB_BRANCH/
artifacts:
- build/**/*.ipa
- build/**/*.dSYM

Artifacts are still collected (visible in dashboard logs), but for long-term storage, use S3, Firebase, or another service.

In a multi-job pipeline, each job runs in its own fresh VM with a fresh repository clone. Files written by one job are not automatically visible to another — with one exception: the needs-based artifact restore.

  1. Producer job — declare artifacts: with glob patterns as normal. The agent uploads matching files when the job finishes.
  2. Consumer job — declare needs: [producerJobKey]. Before the consumer's first step runs, RunnerHub automatically downloads all artifacts from every job listed in needs: and restores them into the consumer's workspace.

Restored artifacts are placed under the directory pointed to by the RUNNERHUB_NEEDS_DIR environment variable, namespaced by producer job key:

$RUNNERHUB_NEEDS_DIR/<producer-job-key>/<relative-path-of-artifact>

For example, if the producer job key is build and it uploaded build/MyApp.ipa, the consumer can read it at:

Terminal window
$RUNNERHUB_NEEDS_DIR/build/build/MyApp.ipa

If the producer job was matrix-expanded (e.g., build × 2 variants), all variants' artifacts are restored, each under its own expanded job key:

$RUNNERHUB_NEEDS_DIR/build-Release/build/MyApp-Release.ipa
$RUNNERHUB_NEEDS_DIR/build-Debug/build/MyApp-Debug.ipa
name: Build and Deploy
platform: ios
environment:
xcode: "16.4"
triggers:
- push
jobs:
build:
name: Build App
steps:
- name: Install dependencies
run: bundle install && pod install
- name: Archive
run: |
xcodebuild archive \
-scheme MyApp \
-archivePath build/MyApp.xcarchive
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath build/MyApp.xcarchive \
-exportPath build/ \
-exportOptionsPlist ExportOptions.plist
artifacts:
- build/**/*.ipa
- build/**/*.dSYM
deploy:
name: Deploy to TestFlight
needs: [build] # Waits for build; restores its artifacts
steps:
- name: Upload to TestFlight
run: |
IPA="$RUNNERHUB_NEEDS_DIR/build/build/MyApp.ipa"
echo "Uploading $IPA"
xcrun altool --upload-app -f "$IPA" \
-u "$APPLE_ID" -p "$APP_SPECIFIC_PASSWORD"

Check these issues:

  1. Job was cancelled — Artifacts are skipped when a job is cancelled; they upload on SUCCESS, FAILED, and TIMEOUT outcomes
  2. Pattern mismatch — Verify glob pattern matches the actual file path
  3. File doesn't exist — Verify the build actually creates the expected files
  4. Permission denied — Ensure files are readable by the RunnerHub process
  5. Free plan — Artifact upload is skipped on the Free plan; no artifacts are stored (see Artifact Retention by Plan)

Use a step to verify the path:

name: Verify
platform: ios
environment:
xcode: "16.4"
steps:
- name: Build
run: fastlane build
- name: Verify Artifacts
run: |
echo "Checking for artifacts..."
find . -name "*.ipa" -o -name "*.dSYM"
ls -la build/
artifacts:
- build/**/*.ipa
- build/**/*.dSYM

If uploads are slow:

  1. Check network conditions
  2. Consider compressing artifacts before uploading
  3. Upload to your own storage (S3, Firebase) in parallel
name: Compress and Upload
platform: ios
environment:
xcode: "16.4"
steps:
- name: Compress and Upload
run: |
tar -czf app.tar.gz build/app.ipa
curl -X POST -T app.tar.gz https://webhook.example.com/upload

Keep Artifacts Minimal Only collect essential build outputs:

artifacts:
- build/**/*.ipa # ✓ Essential
- build/**/*.dSYM # ✓ Essential
# build/**/*.o # ✗ Not needed
# build/DerivedData/** # ✗ Already cached

Use Descriptive Paths Organize artifacts in the build directory with clear names:

name: Build
platform: ios
environment:
xcode: "16.4"
steps:
- name: Build
run: |
fastlane build
# Rename for clarity
cp build/Release-iphoneos/app.ipa build/MyApp-release.ipa
cp -r build/Release-iphoneos/app.dSYM build/MyApp-release.dSYM

Upgrade Plan for Long-Term Storage If you need artifact retention:

  • Free → PAYG/Pro: Get 7-30 day retention
  • Use cloud storage (S3, Firebase) for permanent archival

Use Dashboard Triggers for Important Builds To preserve artifacts, use manual triggers from the dashboard for important builds. Your triggers: field can focus on webhook-driven runs:

triggers:
- push
- pull_request

Click Trigger Build in the dashboard for builds you want to preserve with artifact retention.