Automatic PR Previews with GitHub Actions
An iOS PR preview works like a web deploy preview: every pull request gets its own build and its own link, and reviewers open the build in a browser. Limrun does the build, the asset upload, and the preview URL. GitHub Actions handles the workflow trigger and the PR comment.
What you get on the PR
After the workflow runs, the PR gets a comment with a preview link:
📱 **iOS preview ready**
Preview: https://console.limrun.com/preview?asset=my-app-pr-42.zip&platform=iosOpen the link in a browser. The Limrun console resolves the asset name to a freshly provisioned simulator with that build pre-installed.
Use the packaged Action (recommended)
limrun-inc/ios-preview-action wraps the create, build, upload, and PR-comment steps into one. It cleans up the Xcode sandbox in a post: step automatically, so you don't need a separate cleanup job.
Set LIM_API_KEY in Settings → Secrets and variables → Actions on the GitHub repo, then drop this in:
name: iOS Preview
on:
pull_request:
types: [opened, synchronize, reopened, closed]
permissions:
contents: read
pull-requests: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: limrun-inc/ios-preview-action@main
with:
project-path: .
api-key: ${{ secrets.LIM_API_KEY }}Inputs: project-path, project, workspace, scheme, sdk, api-key, github-token. Outputs: preview-url and asset-name.
The Action posts a markdown-table comment with the platform, the short SHA, and the preview link. closed in the on.pull_request.types array triggers the post-step cleanup when the PR merges or closes.
Custom workflow
Write the workflow by hand when you need finer control over the comment format, an asset-naming scheme different from the Action's default, or a runner outside GitHub Actions. The CLI is plain Node, so the same lim xcode build --upload pattern works on GitLab CI, CircleCI, Buildkite, Jenkins, and anywhere else npm i -g @limrun/cli runs.
End-to-end: build on every PR open or sync, upload under a PR-scoped asset name, post the preview link as a comment. Replace MyApp with your scheme name.
name: iOS Preview
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
pull-requests: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Limrun CLI
run: npm install --global @limrun/cli
- name: Build on Limrun
env:
LIM_API_KEY: ${{ secrets.LIM_API_KEY }}
PR: ${{ github.event.number }}
REPO: ${{ github.event.repository.name }}
run: |
ASSET_NAME="${REPO}-pr-${PR}.zip"
echo "ASSET_NAME=${ASSET_NAME}" >> $GITHUB_ENV
lim xcode create --reuse-if-exists \
--label pr=${PR} \
--label repo=${REPO}
lim xcode build . \
--scheme MyApp \
--upload "${ASSET_NAME}"
- name: Comment with preview link
uses: actions/github-script@v7
with:
script: |
const assetName = process.env.ASSET_NAME;
const url = `https://console.limrun.com/preview?asset=${encodeURIComponent(assetName)}&platform=ios`;
const body = [
'📱 **iOS preview ready**',
'',
`Preview: ${url}`,
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});What each step does
- Checkout. The source tree is what gets synced to the Xcode sandbox.
- Install Limrun CLI.
npm i -g @limrun/cliruns anywhere Node does. The build itself runs on Limrun's Mac fleet, so the job stays on a cheap Linux runner. - Build on Limrun.
lim xcode create --reuse-if-existsprovisions (or reuses) a sandbox tagged with the PR number and repo name, so subsequent pushes to the same PR reuse the same sandbox instead of spinning up a fresh one.lim xcode build . --upload <name>then syncs the source, runsxcodebuild, and uploads the artifact to Asset Storage. Naming the asset after the PR means the next push overwrites it, so the preview link always points at the latest commit. - Comment with preview link. Build
https://console.limrun.com/preview?asset=<name>&platform=iosand post it as a PR comment. Opening the link in the Limrun console provisions a simulator and pre-installs the asset uploaded in step 3.
Clean up when a PR closes
The packaged Action handles this in its post: step. For the custom workflow, add a cleanup job that deletes everything tagged with the closed PR:
cleanup:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- run: npm install --global @limrun/cli
- name: Delete PR instances and assets
env:
LIM_API_KEY: ${{ secrets.LIM_API_KEY }}
PR: ${{ github.event.number }}
REPO: ${{ github.event.repository.name }}
run: |
# Delete Xcode sandboxes for this PR
lim xcode list --label-selector "pr=${PR},repo=${REPO}" --json \
| jq -r '.[].metadata.id' \
| xargs -r -n1 lim xcode delete
# Delete the preview asset
ASSET_NAME="${REPO}-pr-${PR}.zip"
ASSET_ID=$(lim asset list --name "${ASSET_NAME}" --json | jq -r '.[0].id // empty')
if [ -n "$ASSET_ID" ]; then
lim asset delete "$ASSET_ID"
fiTrigger it from the same workflow file by adding closed to the on.pull_request.types array.
Next steps
Was this guide helpful?