For CI and release teams

Move your next build to a dedicated Mac

Build and verify an IPA manually first, then hand the job off to CI. Follow the steps for setup, connection, integration, distribution and day-to-day operations—without rewriting your existing pipeline.

Aim to complete an initial test setup in about an hour; large dependency downloads, Xcode installation and signing preparation can take longer. If your server is already provisioned, start with the connection checks.

Gather what you need first

Choose a server that fits your project

If you build only occasionally and already have an available Mac, continuing to self-host may be more cost-effective. If you merge code several times a day and would rather not manage power, networking and cooling, consider a dedicated hosted server. OSXPort provides a whole physical Mac mini, assigned to one customer—not a virtual machine.

  1. 01

    Check your project baseline

    Prepare a reproducible build command, a way to access your code, the required Xcode version and your dependency lockfiles. Start with one app, one scheme and one branch; don’t turn your first setup into a migration of your entire release pipeline.

  2. 02

    Choose a configuration and term

    On the order page, choose a model, region and daily, weekly, monthly or quarterly term. Port M4 Base suits lightweight test runs; consider Port M4 Plus for builds across multiple branches or simulator workloads, and Port M4 Pro for large projects with multiple targets.

  3. 03

    Save your server details

    After placing your order, find the server address, connection username and credentials in the console. The console’s current status is the source of truth. Add the server ID to your team’s asset register, and never put login passwords in a repository or support ticket.

  4. 04

    Choose a nearby region

    Available regions: Singapore, Japan (Tokyo), South Korea (Seoul), Hong Kong and the US West Coast. Consider the location of your team, repository and dependency sources. Test actual performance with your own pull and upload jobs, as cross-region network performance can vary.

The three configurations are Apple Silicon M4 · 16 GB unified memory · 256 GB SSD, Apple Silicon M4 · 24 GB unified memory · 512 GB SSD, and Apple Silicon M4 Pro · 64 GB unified memory · 2 TB SSD. For your first test, don’t maximize concurrency. Record memory use, disk usage and artifact size for a single build first.

Verify the server before connecting your pipeline

Use the same user for login, builds and exports

For your first connection, verify the server address and username, then check the developer tools. The session below is an example, not live server output. Version numbers illustrate the format; check the actual output on your server.

Check three layers before reinstalling anything

Connection: Connect using the address, port and username shown in the console. Check the host fingerprint on first connection. If it changes unexpectedly, confirm whether the server was replaced rather than skipping verification. For timeouts, check the address and network; for authentication failures, check the username and credentials.

Tools: Run xcode-select -p to confirm the active developer directory, then run xcodebuild -version to record the version. If your project requires a different version, make sure it’s installed before explicitly switching the path.

Project: Install the locked dependencies in the project directory, then run the existing build command manually. A successful interactive login doesn’t mean a background runner can access signing assets. Verify access again as the user that actually runs the runner.

In the example, NODE_USER and NODE_ADDRESS should be set to the values provided in the console; App.ipa is a sample artifact name. If your project manages fastlane with Bundler, use the project’s standard command: bundle exec fastlane gym.

osxport · tokyo-node
$ ssh "$NODE_USER@$NODE_ADDRESS"
$ xcode-select -p
/Applications/Xcode.app/Contents/Developer
$ xcodebuild -version
Xcode 16.4
Build version 16F6
$ cd ~/workspace/App
$ fastlane gym
Creating archive
Exporting archive
Successfully exported and compressed dSYM file
Successfully exported and signed the ipa file
Exported ipa: ./build/App.ipa
$ ls -lh ./build/App.ipa

Pass criteria: remote commands exit successfully, the version meets project requirements and the artifact exists at the expected path. If signing or compilation fails, keep the logs around the first error—not just the final line.

Use your team’s existing CI

Get one manual job working before registering a runner

All three options run on the same physical macOS server. Team sizes below are organizational guidance, not capacity guarantees. Set concurrency based on memory use per build, the number of simulators running at once and disk usage.

GitHub Actions self-hosted runner

Best for: Teams of around 3–15 people already collaborating through workflow files. Register the server to a controlled repository or organization—no separate scheduler needed.

  1. Download the runner package and registration commands for your macOS version and chip architecture from your repository or organization settings.
  2. Register it under a dedicated runner user, add a team-defined label, and point the workflow’s runs-on to that label.
  3. Start with a version check and a build that uses no sensitive information. Then configure the background service, secret injection and artifact uploads.

Avoid: Making a server with signing assets available to untrusted external pull requests. A dedicated host does not isolate workflows from one another.

Review runner acceptance checks

GitLab Runner · shell executor

Best for: Teams of around 3–30 people that manage code and permissions in GitLab. The shell executor uses Xcode and the command-line environment installed on the server.

  1. Install GitLab Runner for macOS and register the runner using the details provided by your project or group.
  2. Choose shell executor, set a dedicated label, and restrict which protected branches and jobs can run on it.
  3. Split dependency installation, compilation, export and artifact storage into independently verifiable stages. Start with one job at a time.

Avoid: Running untrusted customer repositories on the same shell executor. You’re responsible for managing the working directory and host permissions.

Review runner acceptance checks

Jenkins agent

Best for: Teams of around 10–30 people with an existing Jenkins controller and release approval process. Add the server as a macOS agent without migrating the entire pipeline.

  1. Create a dedicated agent in the controller, with its own node label, working directory and run-as user.
  2. Connect it according to your controller’s configuration. Confirm that the required Java version is compatible with the agent environment before enabling it as a persistent agent.
  3. Have test jobs explicitly target the macOS label, then check environment variables, tool paths and artifact archiving permissions.

Avoid: Setting up a new controller just to package an app occasionally. If you already have a lightweight CI system, another service to manage usually isn’t worth it.

Review runner acceptance checks

Online isn’t enough: verify these four items

Verify as the user that actually runs the runner—not just in your personal interactive session.
CheckHow to verifyPass criteria
Working directory and toolsRun whoami, pwd and xcodebuild -versionThe user, directory and Xcode version match the job configuration
Repository and dependency accessUse restricted credentials injected by CI to fetch the specified branch and restore dependencies from the lockfileNo reliance on undeclared tokens or paths in a personal terminal
Signing availabilityArchive and export in a non-interactive jobNo prompts waiting for manual approval; credentials are not written to logs
Reconnection and artifact storageCheck the background service status, then retrieve artifacts and logs after the job completesThe runner reconnects successfully, and artifacts aren’t left only in a temporary working directory

You can register multiple controlled job entry points on the same server, but they still share CPU, unified memory and SSD. Start with one build at a time and increase concurrency gradually based on your measurements. More runner registrations don’t add physical resources.

Code on Windows, build on macOS

Run Flutter’s iOS build stage remotely

Use Windows for coding, commits and log review; run iOS compilation and signing on the remote Mac. A dedicated server doesn’t replace the required Apple Developer Program membership, signing certificate, private key or provisioning profile for your app.

  1. 01

    Pin your dependencies

    Use the same commit and lockfiles, and check compatibility between Flutter, Dart, CocoaPods and Xcode. First run flutter doctor -v to check the toolchain, then run flutter pub get. Don’t introduce absolute paths from another computer into your project.

  2. 02

    Import signing assets

    Transfer certificates, private keys and provisioning profiles using a security method approved by your team. Import the certificate into a dedicated Keychain accessible to the user running the build. Configure the provisioning profile and export options, then check that the bundle identifier, team ID and distribution type match.

  3. 03

    Build and retrieve the artifact

    Run flutter build ipa --release. If your project requires specific export settings, pass the corresponding --export-options-plist. The artifact is usually in build/ios/ipa/. Check the actual output path, then have CI archive the artifact to your team’s storage.

  4. 04

    Know what remote testing covers

    Run simulator tests and automated builds remotely. Keep testing on physical devices for hardware, sensors and interaction checks. React Native teams can also run the iOS build stage on the server, but should restore dependencies using their project’s own scripts.

Caching speeds up builds; it isn’t a backup. Key caches by project, Xcode version and dependency lockfile. If signing fails, check certificate validity, private keys and provisioning profile matches before deleting all build caches.

Prove you can export before setting up uploads

Keep distribution credentials separate from server login credentials

TestFlight distribution requires the app to have the necessary setup in App Store Connect and the appropriate release permissions. An API key authorizes uploads; it doesn’t replace the signing certificate and provisioning profile needed to build.

Setup sequence

  1. Confirm you have an installable artifact. First use fastlane gym or your project’s existing command to archive and export the app. Check the bundle identifier, version and build number.
  2. Create an API key. An authorized team member should create an App Store Connect API key with the appropriate role, record the Key ID and Issuer ID, and store the private key file securely.
  3. Inject it into CI. Store the private key and identifiers in your team’s secrets manager. Generate a restricted configuration file when the job starts and remove it when the job ends. Never commit the configuration or private key to the repository.
  4. Upload and check processing status. Run pilot and verify the app identifier and upload result. A successful upload doesn’t mean the build is available to testers; confirm processing and test distribution separately.

Upload an existing IPA

fastlane pilot upload \
  --api_key_path "$ASC_API_KEY_JSON" \
  --ipa "$IPA_PATH" \
  --skip_waiting_for_build_processing true

ASC_API_KEY_JSON should point to a temporary API key configuration generated in fastlane’s format; IPA_PATH should point to the actual IPA. If you use Bundler, prefix the command with bundle exec.

Keep the app version, build number, commit identifier and upload result. Never print private key contents or full authentication configuration to logs.

Troubleshoot upload failures by error type

For authentication errors, check the Key ID, Issuer ID, private key and role permissions. For a duplicate build number, increment the build number; you don’t need to reconfigure the runner. For signing or app identifier errors, go back to archiving and export. Validate compilation, signing and upload separately so you don’t have to rerun the entire job each time.

Build handover into your pipeline

Keep your server running year-round—and make jobs portable

The server runs 365 days a year. The data center handles power, networking and cooling; your team manages project dependencies, secrets, job configuration and data backups. Report hardware issues by support ticket. Don’t treat a single server as your only irreplaceable copy.

Keep a rebuild checklist up to date

Record runner labels, tool versions, environment variable names, dependency installation steps and signing asset import procedures. Use automation to rebuild the environment so handover doesn’t depend on someone’s memory.

Back up signing certificates and private keys securely, and retrieve build artifacts and logs to team storage. Back up DerivedData and dependency caches if it makes sense, but ensure they can always be regenerated.

Review the setup checklist

Preserve evidence when something goes wrong

Record the server ID, date and time (including time zone), last successful job and a summary of the error. Note separately whether SSH connects, the runner is online and jobs start. Avoid vague reports like “the build broke.”

If you can still log in, export necessary data and logs before pausing new jobs. Include only redacted information in support tickets. If support needs authorized access to troubleshoot, specify the scope; never send private keys or full passwords.

Report an issue in the console

Repeat acceptance checks after a server replacement

After support reviews the issue, confirm the resolution by support ticket. If a server replacement is needed, update the address and credentials using the provisioning details, then verify the host fingerprint again.

Restore dependencies and signing assets, then register or reconnect the runner and run a minimal build. Once the new server can accept jobs, revoke the old connection credentials and any runner registrations you no longer use.

Repeat the connection checks

Before routine disk cleanup, define what to keep: retain artifacts by release version, logs according to your team’s audit requirements and rotate caches on the assumption they can be rebuilt. Don’t clean the working directory or switch the global Xcode path while a build is running.

Hand over billing details with the server

Check expiry dates before release—not just the last reminder

Daily terms suit an initial test or short build project; weekly terms work well for focused project phases; monthly and quarterly terms are better for ongoing nightly builds. Compare the total price for each term. Don’t use the daily rate as your long-term budget.

Check the boundaries before changing the environment

Six common questions from teams taking over a server

Can I install Homebrew and my project’s dependencies?

Manage developer tools and project dependencies as you would on a standard macOS project. Before installing, check the architecture, installation path and permissions, and document versions and steps. For your first setup, reproduce the project’s existing dependencies rather than upgrading everything at once. That makes it easier to identify whether a failure comes from the project or an environment change.

Can I choose my Xcode version?

Choose a version that meets your project’s compatibility requirements, and check what’s installed on the server first. Before installing another version, check disk space, how to obtain it and project requirements; don’t assume a particular version is preinstalled. If multiple jobs share a server, set DEVELOPER_DIR in the job environment where possible, so a global change doesn’t affect other running jobs.

Do I have root access, and should the runner run as root?

The macOS graphical interface and command line are available. Confirm the specific administrator account and available sudo permissions using the credentials provided in the console. If installation requires additional authorization, check first by support ticket. Don’t assume you should connect directly as root, and don’t run ordinary CI jobs as a privileged user. Apply least-privilege access separately to builds, secrets and working directories.

Can multiple people on my team use one server?

You can add the same dedicated server to your team’s workflows, but its physical resources don’t increase with the number of users. Assign separate users or clearly defined job entry points, limit concurrency and agree on when environment changes can be made. Your team manages permissions for each member’s builds and signing assets; the server isn’t an isolated multi-tenant environment.

Can another customer clear my cache?

The entire server is assigned to one customer, so build resources aren’t shared with other customers. Your team’s cleanup scripts, disk management or environment changes can still affect caches. Treat them as rebuildable accelerators, back up code, signing assets and final artifacts separately, and be prepared to restore dependencies from lockfiles if you move to a new server.

What should I include in a support ticket if setup is blocked?

Include the server ID, CI system, commit identifier for the job, date and time (including time zone), and redacted logs around the first failure. Say whether the same command behaves differently in an interactive terminal and in the runner. Remove access tokens, private keys, passwords and business-sensitive information from paths. If support needs to investigate further, they’ll confirm the scope of access with you through the ticket.

Validate your build server with a real job

Start with a daily test run, then decide whether to run nightly builds long term. Port M4 Base starts at $19.7/day. Keep the commit identifier, build logs and artifacts so you can choose based on your own project.