From Slow Builds to Lightning Fast: How We Scaled Mobile CI/CD with GitLab, Fastlane, and tart
If you’ve ever worked on a mobile development team, you know the pain points of continuous integration and delivery (CI/CD): slow builds, conflicts between feature branches, flaky test runs, and the occasional “Why is the deployment broken this time?” moment.
In my journey optimizing CI/CD pipelines for iOS and Android teams, I’ve tested everything from fully-managed services like Bitrise and Codemagic to fully customized environments. We ultimately chose to build our own setup using GitLab CI, Fastlane, and MacStadium - powered by Tart for macOS virtualization.
Here’s what worked, what didn’t, and how you can apply these lessons to build faster, more reliable pipelines.
Why We Went Custom Instead of Fully Managed
Managed CI/CD platforms like Bitrise and Codemagic are fantastic for small teams or early-stage projects. They offer:
- Preconfigured build environments
- Easy integration with GitHub/GitLab
- Minimal setup overhead
But as our team grew, we needed:
- Full control over Xcode and dependency versions
- Custom build caching strategies
- Ability to parallelize builds in ways managed services couldn’t
- Consistent reproducible environments without “ghost” dependency issues
That’s when we switched to MacStadium for dedicated macOS hardware, and Tart to run virtual macOS environments like Docker containers.
What is Tart and Why We Chose It
Tart is a lightweight virtualization tool built on top of Apple’s Virtualization.framework. Think of it as Docker for macOS: you can create and run fully isolated macOS virtual machines (VMs) with near-native performance on Apple Silicon hosts.
Unlike managed CI/CD services where you depend on their prebuilt environments, Tart gives you full control: you decide which macOS version, which Xcode version, and which dependencies your build machines should run. This reproducibility is essential for stable mobile CI/CD pipelines.
Tart is also maintained by Cirrus Labs, who publish up-to-date VM snapshots (like macos-sequoia-xcode:16.4) that come preloaded with common dependencies. This dramatically reduces setup time and ensures your build environments are consistent.
Our Stack at a Glance
- GitLab CI: Orchestrates the entire build/test/deploy flow
- Fastlane: Automates signing, building, and uploading to App Store / Google Play
- MacStadium: Dedicated Macs we control end-to-end
- Tart: Creates lightweight, reproducible macOS VMs for each build
- Parallel runners: Multiple builds running on the same physical Mac for maximum utilization
Key Optimizations We Implemented
1. Build Reproducibility with Tart
One of the biggest CI/CD pain points is the “it works on my machine” problem. Tart solves this by running every build in a clean, ephemeral VM:
- A fresh macOS VM is spun up at the start of each pipeline job.
- The VM comes preloaded with Xcode, CocoaPods caches, Android SDK, Gradle, and more.
- When the job finishes, the VM is destroyed.
This guarantees clean, reproducible builds - no leftover dependencies, no mysterious cache issues.
2. Seamless GitLab Runner Integration
Tart runs each CI/CD job in a fresh, ephemeral macOS VM, ensuring fully reproducible builds and complete isolation from other jobs or leftover dependencies.
This is done via a custom GitLab executor (gitlab-tart-executor) that integrates directly with a self-hosted GitLab Runner.
Setup Overview:
-
Install GitLab Runner and Tart Executor
brew install gitlab-runner brew install cirruslabs/cli/tart brew install cirruslabs/cli/gitlab-tart-executor -
Pull and clone the macOS + Xcode image
tart pull ghcr.io/cirruslabs/macos-sequoia-xcode:16.4 tart clone ghcr.io/cirruslabs/macos-sequoia-xcode:16.4 macos-sequoia-xcode-min -
Start GitLab Runner and register it
Registration will create a
config.tomlfile automatically, usually located at:~/.gitlab-runner/config.toml
-
Configure the custom Tart executor
After registration, edit the
config.tomlto use Tart for the runner:[[runners]] name = "YourRunnerName" url = "https://gitlab.com" executor = "custom" [runners.custom] config_exec = "/opt/homebrew/bin/gitlab-tart-executor" config_args = ["config"] prepare_exec = "/opt/homebrew/bin/gitlab-tart-executor" prepare_args = ["prepare", "--auto-prune", "false", "--concurrency", "1", "--cpu", "4", "--memory", "12288", "--dir", "CocoaPods:~/Library/Caches/CocoaPods"] run_exec = "/opt/homebrew/bin/gitlab-tart-executor" run_args = ["run"] cleanup_exec = "/opt/homebrew/bin/gitlab-tart-executor" cleanup_args = ["cleanup"]
Once configured, every CI/CD job runs in a clean Tart VM, giving Docker-like isolation for iOS and Android builds.
Example GitLab CI Job:
variables:
TART_EXECUTOR_ALWAYS_PULL: "false"
image: macos-sequoia-xcode-min
tags:
- tart
build_ios:
stage: build
script:
- fastlane ios build
3. Automating Tests with GitLab + Fastlane
We automated our testing strategy to ensure reliability and save developer time:
- Unit tests run on every push.
- UI tests run nightly and before releases.
- Fastlane handles simulator installs, running XCTest/Espresso, and uploading reports as GitLab artifacts.
Automation catches regressions early and frees developers from manual checks.
4. Improving Team Workflow
To reduce merge conflicts in our CI/CD configs:
- We split CI configs into separate YAML files per stage (build, test, deploy).
- We used
include:to assemble them in GitLab. - We kept Fastlane lanes modular, so new deployment targets didn’t break unrelated configs.
5. Faster Deployment Cycles
Our deployment process dropped from 20+ minutes to much faster thanks to:
-
Build artifact reuse across stages: Instead of rebuilding the app multiple times (for tests, distribution, etc.), we build once and pass the artifact through GitLab’s cache/artifacts system.
-
Parallelization: iOS and Android builds now run in parallel, cutting down total pipeline runtime.
-
Optimized upload process: Using Fastlane’s pilot and supply with API key authentication avoids manual steps and reduces delays when pushing builds to TestFlight and Play Store.
These improvements cut our iOS release time by ~40%.
Lessons Learned
- Don’t over-optimize too early: start with Bitrise/Codemagic if your team is small.
- Virtualization (Tart) is a game changer for reproducible, isolated builds.
- Parallelization is key for scaling heavy projects.
- Automate tests aggressively to avoid surprises.
- Modular configs reduce merge conflicts and simplify onboarding.
Final Thoughts
CI/CD is not just a “devops problem”; It’s a productivity multiplier. By moving to a fully controlled MacStadium + Tart setup and combining it with GitLab + Fastlane automation, we’ve made our mobile build pipeline:
- Faster
- More reliable
- Easier to maintain
The result? Developers spend less time waiting and more time building features that matter.
If you’re hitting scaling issues with managed CI/CD platforms, consider taking control of your build environment. It’s more work upfront, but the speed, reliability, and flexibility gains are worth it.
Comments
Post a Comment