SwiftPM Private Dependencies: 2026 Remote Mac Setup Guide

SwiftPM private dependencies should use dedicated repository credentials, a committed Package.resolved, and SSH settings owned by the macOS user that actually runs the build. This approach fits developers moving from a local Mac to a persistent remote Mac, especially when command-line or scheduled builds must work without an open Xcode session.
This guide is for independent developers and small teams with private Swift packages. It focuses on credential boundaries, reproducible dependency resolution, command-line archive checks, unattended execution, and restart recovery.
The migration baseline
A common failure looks like this: Xcode can build the project through its graphical interface, but a background task on the same remote Mac cannot fetch a private package. The two actions may use different macOS users, SSH environments, Keychain access, working directories, or authentication state.
The first decision is therefore not which SSH command to copy. It is identifying the real build principal.
The build principal is the macOS user and process context that executes one of the following:
- Xcode during a manual archive.
xcodebuildfrom an SSH session.- A scheduled task.
- A CI runner or launch service.
- A shell script started by a remote console.
Apple’s continuous integration guidance treats package resolution as part of the build environment, not as a side effect of a developer’s open project. Its guidance also explains why Package.resolved matters when a project is built outside the developer’s normal machine. See Apple’s Swift package continuous integration guidance before changing the repository layout.
Dependency inventory
Create a short inventory before connecting the remote Mac:
- Direct private packages declared by the app or workspace.
- Transitive packages pulled in by those direct dependencies.
- The repository URL used by each package.
- The branch, tag, or revision currently used.
- The minimum repository permission needed for a read-only build.
- The current
Package.resolvedcommitted with the project.
A private package can be hidden several levels below the application target. Checking only the app’s manifest can miss a transitive repository that still requires authentication. Apple’s Package dependency documentation describes the dependency declarations that determine how SwiftPM resolves package sources and versions.
The migration baseline should be a successful local build with a known commit. Do not begin by allowing the remote Mac to select newer package versions. First reproduce the state that already works.
Credential boundary
Personal development credentials often have more access than a build needs. They may allow repository writes, issue management, package administration, or release operations. A remote build host should not inherit those permissions merely because it needs to read source code.
Use a separate SSH key for the build user. Register only the corresponding public key with the required private repositories. Keep code-signing credentials separate from source dependency credentials. Reading a package must not automatically grant permission to publish an application.
| Option | Access scope | Best use | Main risk |
|---|---|---|---|
| Personal SSH key | Usually broad and tied to a person | Interactive development | A host compromise can expose unrelated projects |
| Dedicated read-only build key | Limited to selected repositories | Remote dependency resolution | Requires rotation and repository inventory |
| Shared team key | Difficult to attribute | Legacy environments only | Weak ownership, weak revocation, unclear audit trail |
| Token in a build script | Depends on token policy | Temporary migration testing | Easy to leak through logs, files, or shell history |
The dedicated key is the default choice for a persistent remote build host. If the repository platform cannot limit a key to the needed repositories, the team should treat the key as a wider credential and reduce its lifetime and storage exposure.
The build user and SSH environment
Swift Package authentication is not configured “on the Mac” in the abstract. It is configured for a user, a home directory, an SSH client, and a process environment.
If a scheduled task runs as builduser, placing an SSH key under another administrator’s home directory does not solve the build. A successful VNC session under a personal account is also not proof that the scheduled task can authenticate.
User-specific configuration
Log in as the intended build user, or use an equivalent controlled session. Then confirm:
- The home directory used by the process.
- The repository URL scheme.
- The location of the user’s SSH configuration.
- The host key file available to that user.
- Whether an SSH agent or Keychain integration is expected.
- The working directory used by the build script.
A repository URL using SSH must match the host alias and key rules configured for that user. If the project uses an HTTPS URL while the key was prepared for SSH, the key may never be consulted. Change the repository URL deliberately and review the resulting package identity rather than mixing authentication methods.
When the Mac is rented for testing, keep the connection details and build-user ownership visible in the ProxyMac console. The console can help separate a remote access problem from a repository authentication problem, but it does not replace testing the exact non-graphical build process.
The official SSH key generation and agent documentation explains the relationship between a private key, its public key, and the agent. The operating-system page differs in places, but the security principle is the same: the private key stays on the build host and the public key is registered with the repository service.
Do not paste a private key into a project file, shell script, environment file committed to Git, or a command copied into a ticket. Do not place a password in a command line that can be retained in terminal history.
Host verification
A private key proves possession of a credential. It does not replace server identity verification. The build user also needs a trusted host key entry in known_hosts.
This matters on a remote Mac because an interactive first connection can silently create state that a scheduled task does not have. A VNC login may accept a host confirmation prompt. A non-interactive archive cannot answer that prompt safely.
Use the repository service’s documented connection test after configuring the key and host entry. The official SSH connection test instructions provide the expected validation pattern. The useful result is not simply “SSH works.” The test must work as the same user who will resolve the package.
Stop condition: If the connection requires accepting an unknown host interactively, or if it works only under a personal account, stop before running
xcodebuild. Disabling host verification is not a durable fix.
Minimal validation matrix
| Check | Manual GUI session | Build-user shell | Unattended task |
|---|---|---|---|
| Correct macOS user | May be different | Must match | Must match |
| SSH private key access | Often implicit | Explicitly test | Test without VNC |
known_hosts access |
May be created interactively | Must already exist | Must already exist |
| Repository read access | Xcode may retain state | Test directly | Test from the task |
| Package lock visibility | Project-dependent | Confirm path | Confirm clean workspace |
| Failure output | Xcode panel | Shell log | Persisted build log |
This table is a decision tool. If the build-user shell passes but the unattended task fails, the problem is session state or environment inheritance. If both fail, investigate repository URL, key scope, host verification, or repository permissions before touching Xcode settings.
The first independent package resolution
Dependency resolution should be tested before compilation. Combining authentication, version selection, compiler errors, and signing failures in one build makes the first failure harder to classify.
Package.resolved as the version boundary
For a committed application or workspace, Package.resolved should normally be reviewed and committed when the goal is reproducible continuous integration. Apple’s CI documentation explicitly covers committing this file for CI workflows. It records the resolved package state that the build is expected to use.
The file should be checked for:
- Its presence in the repository.
- Its location relative to the project or workspace.
- The private package identity.
- The recorded revision or version.
- Unexpected changes introduced during migration.
- Consistency between the local project and the remote checkout.
Should Package.resolved be committed?
For a project that must reproduce dependency versions in a remote build, yes, provided the team reviews changes to it. The exception is a package-development workflow where the package itself is intentionally testing fresh dependency resolution. That is a different objective from archiving an application.
Do not delete the lock file merely because authentication fails. A missing lock file can cause a new resolution attempt, which introduces version drift while the original credential problem remains.
Resolution-only run
Use the same project type, workspace path, scheme context, and build user that the later archive will use. Apple’s command-line build note documents the command-line model for xcodebuild and its project or workspace arguments.
A minimal resolution check can use the documented xcodebuild dependency-resolution action:
xcodebuild \
-resolvePackageDependencies \
-workspace App.xcworkspace \
-scheme App
The names above are placeholders for the real workspace and scheme. The important parameter is -resolvePackageDependencies: it isolates package retrieval and resolution before compilation. Keep the output in a protected build log. Do not include private key material in that log.
The expected result is more specific than “the command completed.” The build user should be able to show:
- The private package was reached through the intended repository URL.
- The existing lock state was recognized.
- No unexpected package revision was selected.
- The package checkout is available to the later build.
- Authentication errors are absent.
If the command fails, stop at this stage. Classify the failure as repository authorization, SSH host verification, repository URL mismatch, lock-file location, or package version resolution. Do not move to code signing until the dependency boundary is clean.
Common authentication mismatch
Why can xcodebuild fail to fetch a private dependency when Xcode succeeds?
The graphical session may have credentials, an agent socket, or cached account state that the command-line process does not inherit. The command may also run as another macOS user or from another home directory. Re-run the resolution action in the actual build context. If the SSH connection test fails there, fix SSH first. If SSH passes but SwiftPM still fails, compare the package URL and lock-file location.
The first build and archive
Once resolution passes independently, run a real build from the same user and directory used by the future automation. A graphical archive is useful for comparison, but it is not sufficient evidence for an unattended workflow.
The first command-line build should verify three separate outcomes:
- Private packages are available to the target.
- Application targets compile with the intended scheme.
- The project can produce an archive using the planned signing context.
Use the project or workspace arguments required by the repository. If the project uses a workspace, do not silently substitute the project file. If the build needs source-control provider behavior, review the available xcodebuild SCM options in Apple’s command-line documentation rather than adding undocumented flags.
For an archive, keep the archive path outside the source checkout and make it predictable for the task. Apple’s archive and distribution documentation explains the archive and export stages. The same operational distinction matters for iOS pipelines: creating an archive is not the same as exporting or uploading it.
The acceptance record should include:
- The commit checked out.
- The visible
Package.resolvedstate. - The package-resolution output.
- The exact scheme and workspace used.
- The archive result.
- The location of the archive.
- The reason for any failure.
A failure at archive signing is not a private dependency failure. Keep those diagnoses separate. This separation prevents a team from widening repository permissions to solve a certificate or provisioning issue.
Unattended sessions and credential isolation
A remote Mac becomes a build host only when the workflow works without VNC, without an active personal login, and after an SSH session disconnects.
Agent and Keychain behavior
Test the task in the same non-graphical context it will use in production. An SSH agent may exist in an interactive shell but not in a scheduled process. Keychain access may also depend on login state, approval prompts, or the user’s session.
The SSH passphrase guidance for macOS explains how an agent can avoid repeatedly entering a passphrase. That convenience must be tested against the actual task context. A key that is available only after a manual VNC login is not an unattended build credential.
Use explicit stop conditions:
- Stop if the task asks for an interactive passphrase.
- Stop if it depends on a personal agent socket.
- Stop if the private key is readable by unrelated users.
- Stop if a repository read key is also used for code signing.
- Stop if the script disables host verification.
- Stop if a failed authentication attempt triggers a fallback to an unapproved credential.
Environment variables can help pass non-secret configuration, but they should not become a dumping ground for private keys or long-lived tokens. Store credentials according to the operating system and repository provider’s documented controls. Record who can rotate them and how a revoked key is removed from the remote Mac.
Script shape
A reliable unattended job should make its stages visible:
set -e
xcodebuild -resolvePackageDependencies \
-workspace App.xcworkspace \
-scheme App
xcodebuild archive \
-workspace App.xcworkspace \
-scheme App \
-archivePath "$PWD/build/App.xcarchive"
The first action isolates dependency resolution. The second tests compilation and archive creation. The exact signing and export settings belong to the project’s release policy and should not be guessed in a generic guide.
The script should write logs to a protected location, return a failure status, and avoid printing secret environment values. The task owner should be able to tell whether the failure happened before package resolution, during compilation, or during archive signing.
Restart and dependency-change acceptance
A remote Mac that works only because its cache is warm is not ready for long-running builds. Reboot recovery is a separate acceptance stage.
Clean-workspace replay
After configuring the task, replay the workflow from a clean, reconstructable checkout:
- Start with the intended build user.
- Remove only caches that the team can safely regenerate.
- Confirm the repository checkout and lock file.
- Run dependency resolution.
- Run the build and archive.
- Preserve the logs.
- Disconnect the remote session.
- Run the task again without VNC.
The purpose is not to destroy every system cache on every build. It is to prove that the documented process can reconstruct the package state and does not depend on an undocumented interactive action.
For remote access and recovery procedures, keep the host’s operational notes with the ProxyMac help resources. Those resources can support access troubleshooting, but package authentication still needs to be verified inside the actual build-user session.
What should happen if the SSH key stops working after a remote Mac reboot?
First check whether the task user can read the key, whether the agent was reloaded, and whether the Keychain integration is available in that session. Then confirm that known_hosts is present for the same user. Do not create a new key immediately. A reboot failure often reveals that the original workflow depended on a GUI login or an agent socket that was never documented.
If the key is intentionally loaded by an agent, define how the agent starts after reboot and how its access is protected. If that cannot be made reliable, use a credential design that matches the task’s security and availability requirements instead of adding an insecure fallback.
Package updates and rotation
Treat a private package update as a controlled change:
- Update the dependency from a trusted development context.
- Review the resulting
Package.resolveddiff. - Confirm the new revision belongs to the intended repository.
- Run resolution as the build user.
- Run a clean archive.
- Revoke or rotate credentials when a developer leaves, a repository is removed, or the build user changes.
- Repeat the checks after a major Xcode or repository authentication change.
A key rotation is incomplete until the old public key is removed from the repository service and the old private key is removed from the remote Mac. Repository removal also deserves a test: the build should fail clearly when a required package is no longer available, rather than silently selecting a different source.
When a remote Mac is the right deployment target
A local Mac is convenient for interactive debugging, but a dedicated local machine can remain logged in, lose disk space to package and build artifacts, or become unavailable when the developer travels. A generic cloud build service can simplify infrastructure, yet its environment, session behavior, package authentication controls, and debugging access may not match a project with several private repositories.
A remote Mac offers a middle path when the project needs a persistent macOS user, SSH access, Xcode, and control over the build directory. It is especially suitable for a developer who wants to test a real cold-start workflow before committing to a longer deployment.
The important qualification is operational: renting a Mac does not fix an incorrectly scoped key. The deployment still needs a dedicated credential, a committed lock file, a tested build user, and a reboot procedure. Once those controls are written down, a short-term ProxyMac remote Mac environment can be used to run the real project through resolution, archive, and restart acceptance before it becomes a permanent iOS build host.
Compared with keeping a GUI-only local setup, the current approach can leave builds dependent on one person’s login state, consume the developer’s local disk, and fail when the machine is asleep or unavailable. Compared with an opaque shared build environment, it can make SSH ownership, repository access, and recovery behavior harder to inspect. ProxyMac is the more practical fit when the project needs a persistent Mac environment for a temporary migration, a release cycle, or a validated unattended build path—provided the team first proves that the private dependency workflow survives a clean session and reboot.
For a hands-on test, the next step is not a blind long-term commitment. Rent a short-cycle Mac environment, run the actual project through SSH validation, independent package resolution, archive creation, and restart recovery, then extend the arrangement only after the unattended job can read its private packages without a personal Xcode session.
Move Your SwiftPM Builds to a Dedicated Remote Mac
Deploy a dedicated Mac mini M4 for private-package resolution, archiving, and continuous build jobs.
Connect through SSH or browser-based VNC and manage your remote build environment from the ProxyMac console.