Team CI setup guide

Set up your runner with this guide

This Apple Silicon M4 Mac mini runs your team’s jobs—and no one else’s. Check the instance and connection first, register your runner, then pin the Xcode toolchain. Troubleshoot the step that failed instead of rebuilding the entire pipeline.

Check existing orders in the portal. Keep runner registration tokens, SSH passwords and signing materials out of public logs.

From order to connection

Get your node ready in four checks

Allow about 4 minutes from payment confirmation to receiving and checking your connection credentials. These times are a guide and exclude payment gateway confirmation, network retries, dependency installation and runner registration. Check the portal for the latest status.

  1. Confirm your order

    About 30 sec

    In the portal, confirm that your order is paid, then check that the model, billing period and node match your purchase. Merge M4 S has 16GB RAM / 256GB SSD; Merge M4 L has 24GB RAM / 512GB SSD. Both models use Apple Silicon M4.

    Stuck here? Check the billing status first. Don’t pay again just because the page hasn’t updated yet. Keep your order number, then check the payment record in the billing section below.

  2. Check your node assignment

    About 2 min

    Open the instance linked to your order and wait for its status and connection details to appear. Each machine is assigned to one customer; it is not a virtual machine. Your configured runner handles job scheduling within your team.

    Stuck here? Note the instance ID, order number and current status, then submit a support ticket through the portal. A paid order does not necessarily mean the instance credentials are ready.

  3. Get your connection details

    About 1 min

    In the instance details, find the SSH address, port and username, along with the VNC connection details. Use the values shown for each field; don’t guess the default port or reuse an old instance’s address or password.

    Stuck here? Make sure the account you’re signed in to owns the order, then check that you’ve opened the right instance. Your ticket only needs the instance ID and the missing field name—never send your full password.

  4. Test a remote connection

    About 30 sec

    Use SSH for command-line builds, runner registration and file transfers. Use VNC to work with the Xcode interface, simulators and interactive settings. You can use both the macOS graphical interface and the command line, and switch between them as needed.

    ssh -p "$SSH_PORT" "$SSH_USER@$SSH_HOST"
    uname -m
    sw_vers

    On your first connection, verify the host fingerprint. Once connected, check that the architecture is reported as arm64. If the connection times out, check the address, port and local network. If authentication fails, check the username and credentials. Don’t bypass a fingerprint warning by disabling host verification.

    In your VNC client, use the relevant values from the instance details—not the SSH port. If the connection works but the screen feels sluggish, try a lower resolution. Automated builds can continue over SSH.

Use your existing CI platform

Route jobs to your M4

Start on your CI platform’s page for adding a runner or agent. Get the installer and registration command, then run them on the node. The examples below cover registration only: your platform generates the token, organisation URL and working directory, so don’t copy the placeholder variables as-is. After installation, run a test job without signing materials.

GitHub Actions

Add a self-hosted runner in your repository or organisation settings, then choose the macOS ARM64 package. Extract it, run the configuration command provided by GitHub from the runner directory, and start the process.

./config.sh \
  --url "$RUNNER_URL" \
  --token "$RUNNER_TOKEN" \
  --labels macos-m4
./run.sh

Set runs-on: [self-hosted, macOS, ARM64, macos-m4] in your workflow and make sure the labels match the runner registration. Labels route jobs; they don’t automatically isolate them from one another.

Check that the platform shows the runner as online and that a test job reports arm64 from uname -m. To keep it running, configure the service as described by the installer and run it as the same build user.

Suggested label: macos-m4

GitLab Runner

Create a runner for your project or group and get the authentication token for your current version. Install the ARM64 build and register it with the shell executor so jobs run in that build user’s macOS environment.

gitlab-runner register
gitlab-runner verify
gitlab-runner run

Set the runner’s tag to macos-m4 in GitLab, then use the same value in your job’s tags. During interactive registration, enter the URL and token provided by your platform. Never put the token in your repository.

Check that the runner is assigned to the right project and that jobs start running. If a job stays pending, check its tags, protected branch settings and runner access to the project, then check the machine’s load.

Executor: shell

Jenkins agent

Create a dedicated node and set its remote working directory and macos-m4 label. Install a compatible Java version as directed by your controller, download its agent file and use the launch arguments it generates.

java -jar agent.jar \
  -url "$JENKINS_URL" \
  -secret "$AGENT_SECRET" \
  -name "$AGENT_NAME" \
  -webSocket \
  -workDir "$AGENT_WORKDIR"

In your pipeline, use agent { label 'macos-m4' }. The build user must be able to write to the working directory. Match the Java version to the controller’s requirements; don’t just check that Java starts in your local terminal.

Check that the controller shows the node as online and that its workspace is in the expected directory. Start with a single executor to establish a baseline, then assess concurrency against memory, disk space and test load.

Start with one executor

A dedicated machine removes competition for CPU from other customers, but it won’t manage your team’s internal job queue. If one runner handles several jobs at once, you’ll still need to manage concurrency, workspaces and signing material isolation.

Keep builds reproducible

Use the same Xcode locally and in CI

“It builds in the terminal, but the runner fails” often points to a different toolchain path or runtime user. Record the current versions, select the toolchain, then install any required simulator runtimes. Reinstalling Xcode shouldn’t be your first step.

Record versions before switching

You can keep multiple Xcode versions in separate application paths. First, use these commands to record the system, active developer directory and build tool versions, then compare them with your project requirements.

sw_vers
xcode-select -p
xcodebuild -version
xcrun --find swift

To change the machine-wide default, use xcode-select. Check that no other builds are running before switching, then verify the result as the CI runtime user.

sudo xcode-select --switch \
  /Applications/Xcode.app/Contents/Developer
xcodebuild -version

Choose per job, install per project

If your team needs different Xcode versions, set DEVELOPER_DIR for each job where possible. This avoids interfering with the machine-wide setting. Replace the example path with a version directory that actually exists on your node.

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
xcodebuild -version
xcrun simctl list runtimes
xcrun simctl list devices available
df -h

Download simulator runtimes in Xcode settings to match each project’s targets. Runtimes, simulator data and DerivedData all use disk space. Check available space before installing and keep only the versions your team uses.

A working command doesn’t mean the project has all its dependencies. Check Ruby, CocoaPods, package managers and lockfiles as the same user. Confirm any first-run installation prompts in an interactive session.

Before your first build, check these three things in the runner environment
CheckCommand or locationPass criteria
Toolchain pathxcode-select -p and job environment variablesPoints to the Xcode version specified by the project, and the reported version matches expectations
Runtime identitywhoami, printf '%s\n' "$HOME"The build user, home directory and keychain owner are consistent
Workspace and diskpwd, df -hThe workspace is writable, with enough space for dependencies, temporary files and archives

Speak the same language

Eight terms, eight clear distinctions

Keep the machine, connection methods and job scheduling separate in your mind, and you’ll be able to pinpoint the issue when asking for help.

Physical node
The Mac mini that runs macOS and your build jobs. The connection details in your instance correspond to the node you rent.
Dedicated
The whole machine is assigned to one customer, so you don’t share its CPU with other customers. Your team still needs to manage its own job concurrency.
Not a VM
You get access to a dedicated physical Mac, not a virtual instance carved out of a shared host.
VNC
A way to view and control the macOS graphical interface remotely. Useful for Xcode, simulators and interactive setup.
SSH
An encrypted command-line connection for running build commands, managing processes and transferring files with compatible tools.
Self-hosted runner
A CI worker your team registers and manages on its own node. It picks up and runs jobs from your CI platform.
Apple Silicon
The chip architecture used by Mac computers. Both models in this service use M4; check for arm64 support when choosing dependencies and runner installers.
Thunderbolt 5 linking
An optional connection add-on billed per machine. It doesn’t combine the CPU or memory of multiple machines into one; you still need to orchestrate workloads yourself.

Keep the logs; change one thing at a time

Fix the layer where the build failed

Save the failed command, exit code and first meaningful error, then change one variable and retry. If you switch Xcode, clear caches and re-register the runner all at once, you won’t know which step fixed the issue.

Signing failed? Check the runtime user and keychain first

First, work out whether the failure is during compilation or signing. Check the signing certificate, its private key, provisioning profile and expiry only when the error occurs during archiving or signing. Your team must configure these materials for the project.

whoami
security list-keychains -d user
security find-identity -v -p codesigning

If signing works in the graphical interface but fails in CI, check that the runner uses the same user, that the right keychain is in its search list, and that non-interactive processes have the required access. If the keychain needs unlocking, inject credentials through your CI platform’s protected secrets. Never put passwords in scripts, shell history or logs.

After importing a certificate, also check that the provisioning profile matches the team, app identifier and build configuration. Include a redacted error in your support ticket; never upload private keys, certificate passwords or complete signing materials.

Cache issue? Narrow it down to one project before cleaning

Retry the same commit first and note the Xcode version. If only one dependency fails to resolve, check the lockfile, dependency version and download error. Repeatedly clearing every cache won’t fix a network problem.

Give each project its own DerivedData path so you can rebuild just that project’s outputs. The command below is for an existing, dedicated workspace. Replace the variables with real paths and a shared scheme before running it.

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -derivedDataPath "$PROJECT_DERIVED_DATA" \
  clean build

clean means more work on the next build. Don’t let multiple jobs write to the same cache directory. Stop any jobs using it before cleaning or moving it.

Low on disk space? Check caches, runtimes and build outputs separately

Check available space, then find out how much is used by DerivedData, archives and simulator data. Don’t recursively delete the entire user directory, and don’t remove build outputs while an archive is being uploaded.

df -h
du -sh "$HOME/Library/Developer/Xcode/DerivedData"
du -sh "$HOME/Library/Developer/Xcode/Archives"
du -sh "$HOME/Library/Developer/CoreSimulator"

Stop affected jobs, remove project caches you no longer need, uninstall unused runtimes in Xcode, and manage backed-up archives according to your team’s retention policy. Back up source projects, signing materials and release artifacts separately.

If you need more space, consider the +1TB SSD or +2TB SSD add-on when configuring your order. More storage doesn’t mean more memory; check that disk space, rather than job concurrency, is the bottleneck.

Runner offline? Check connectivity, process and access in turn

First, use SSH to confirm that the node is reachable, then check the runner process and logs. If the node is online but your CI platform shows the runner as offline, check whether the process exited, whether it can reach the CI controller, and whether its token or node permissions have changed.

Run the runner in the foreground once and keep the first connection error. If it works in the foreground but stops when you close the terminal, configure a persistent service as described for that runner. The service user must match the user configured for the toolchain, working directory and keychain.

Before restarting, make sure no archive or upload job is running, so you don’t interrupt its output. If the platform shows the runner as online but jobs remain queued, check labels, branch permissions and concurrency limits rather than repeatedly deleting and re-registering it.

If you have multiple instance groups in a pool, use distinct labels such as macos-m4-build and macos-m4-release. This lets release jobs run only on nodes configured with the required signing environment.

Check the transaction before retrying

Match each payment to its order

Choose daily, weekly, monthly or quarterly billing. All charges are settled in US dollars (USD). We accept USDT-TRC20 and Visa / Mastercard / Amex processed by Stripe. Available payment gateways are confirmed by the backend.

USDT-TRC20 sent, but the order is still pending

Check the network, receiving address and amount shown on the order payment page. A transfer record doesn’t mean the order has been confirmed. Check both the on-chain transaction status and the billing status in the portal.

Don’t send a second transfer to speed up confirmation, and don’t reuse payment details from another order. Keep the order number, transaction hash, amount and transfer time, then include them in a portal support ticket. Never share wallet keys or recovery phrases.

If you’ve closed the payment page, return to the same order to check its current status before deciding whether to continue. This helps avoid duplicate payments for the same service.

Card payment didn’t go through? Check verification and your issuer’s response

Check that your card and billing details are correct, that you have enough available credit and that issuer verification is complete. If a page was blocked during the redirect, allow the page required for the current payment flow to open, then check the order again.

If you see a decline message, save a summary of the error and ask your card issuer about any transaction restrictions. Don’t submit the payment repeatedly in quick succession. If your bank shows a charge but the order isn’t confirmed, check with support before trying again.

Your ticket only needs the order number, time of the issue and a redacted screenshot of the error. We never need your full card number, security code or bank verification code.

Before renewing, check the instance, billing period and expiry date

Open the subscription or billing record for your existing instance in the portal. Check the instance ID, expiry date and billing period before paying. A new order and a renewal for an existing instance are different things—don’t rely on the model name alone.

Use the billing and instance status to confirm that a renewal is complete; don’t assume it renewed automatically. Back up your projects, cache configuration and release artifacts before expiry. Disk erasure after an instance is released is not a recoverable backup.

Nodes run normally 365 days a year. If you’re reporting an availability issue, include the instance ID, affected time period and connection error so we can investigate. Distinguish a process stopped by a user from an unavailable node.

Share details we can reproduce

Give support a clear picture of the failure

For an existing instance, submit a support ticket through the portal with its instance ID, the time and time zone, the failed step, the expected result and redacted logs. For batch provisioning or runner pool planning, outline your requirements on the contact page or email us at support@vpsmerge.com.

Never send passwords, registration tokens, private keys or full payment credentials. The first meaningful error is more useful than pages of repeated logs.