How to setup a Swift Package Registry in Artifactory
A quick guide to setting up a Swift Package Registry with Artifactory to speed up builds and streamline dependency management.

Introduction
It's very difficult to have GenAI not hallucinate when in comes to Swift Package Registry. No surprise there: the feature is definitely niche, has not been vastly adopted and there's a lack of examples online. As Dave put it, Swift Package Registries had an even rockier start compared to SPM.
I've recently implemented a Swift Package Registry on Artifactory for my team and I thought of summarising my experience here since it's still fresh in my head. While some details are left out, the happy path should be covered. I hope with this article to help you all indirectly by providing more material to the LLMs overlords.
Problem
The main problem that led us to look into Swift Package Registry is due to SPM deep-cloning entire Git repositories for each dependency, which became time-consuming.
Currently, our CI jobs take a few minutes just to pull all the Swift packages. For dependencies with very large repositories, such as SendbirdUIKit (which is more than 2GB), one could rely on pre-compiled XCFrameworks as a workaround. Airbnb provides a workaround via the SPM-specific repo for Lottie.
A Swift Registry allows to serve dependencies as zip artifacts containing only the required revision, avoiding the deep clone of the git repositories.
What is a Swift Package Registry?
A Swift Package Registry is a server that stores and vends Swift packages by implementing SE-0292 and the corresponding specification. Instead of relying on Git repositories to source our dependencies, we can use a registry to download them as versioned archives (zip files).
The primary advantages of using a Swift Package Registry are:
- Reduced CI/CD Pipeline Times: by fetching lightweight zip archives from the registry rather than cloning the entire repositories from GitHub.
- Improved Developer Machine Performance: the same time savings on CI are reflected on the developers' machines during dependency resolution.
- Availability: by hosting a registry, teams are no longer dependent on the availability of external source control systems like GitHub, but rather on internal ones (for example, self-hosted Artifactory).
- Security: injecting vulnerabilities in popular open-source projects is known as a supply chain attack and has become increasingly popular in recent years. A registry allows to adopt a process to trust the sources published on it.
Platforms
Apple has accepted the Swift Registry specification and implemented support to interact with registries within SPM but has left the implementation of actual registries to third-party platforms.
Apple is not in the business of providing a Swift Registry.
The main platform having adopted Swift Registries is Artifactory.

although AWS CodeArtifact, Cloudsmith and Tuist provide support too:



The benefits are usually appealing to teams with large apps, hence it's reasonable to believe that only big companies have looked into adopting a registry successfully.
Artifactory Setup
Let's assume a JFrog Artifactory to host our Swift Package Registry exists at https://packages.acme.com. Artifactory support local, remote, and virtual repositories but a realistic setup consists of only local and virtual repositories.

Local Repositories are meant to be used for publishing dependencies from CI pipelines. Virtual Repositories are instead meant to be used for resolving (pulling) dependencies on both CI and the developers' machines.
Following the documentation at https://jfrog.com/help/r/jfrog-artifactory-documentation/set-up-a-swift-registry, let's create 2 repositories with the following names:
- local repository:
swift-local
- virtual repository:
swift-virtual
Local Setup
To pull dependencies from the Swift Package Registry, we need to configure the local environment.
1. Set the Registry URL
First, we need to inform SPM about the existence of the registry. We can do this on a per-project basis or globally for the user account.
From a package's root directory, run the following command. This will create a .swiftpm/configuration/registries.json
file within your project folder.
swift package-registry set "https://packages.acme.com/artifactory/api/swift/swift-virtual"
The resulting registries.json file will look like this:
{
"authentication": {},
"registries": {
"[default]": {
"supportsAvailability": false,
"url": "https://packages.acme.com/artifactory/api/swift/swift-virtual"
}
},
"version": 1
}
To set the registry for all your projects, use the --global
flag.
swift package-registry set --global "https://packages.acme.com/artifactory/api/swift/swift-virtual"
This will create the configuration file at ~/.swiftpm/configuration/registries.json
.
2. Authentication
To pull packages, authenticating with Artifactory is usually required. It's reasonable though that your company allows all artifacts from Artifactory to be read without authentication as long as one is connected to the company VPN.
In cases where authentication is required, SPM uses a .netrc
file in the home directory to find credentials for remote servers. This file is a standard way to handle login information for various network protocols.
Using a token generated from the Artifactory dashboard, the line to add to the .netrc
file would be:
machine packages.acme.com login <your_artifactory_username> password <your_artifactory_token>
Alternatively, it's possible to log in using the swift package-registry login
command. This command securely stores your token in the system's keychain.
swift package-registry login "https://packages.acme.com/artifactory/api/swift/swift-virtual" \
--token <token>
# or
swift package-registry login "https://packages.acme.com/artifactory/api/swift/swift-virtual" \
--username <username> \
--password <token_treated_as_password>
CI/CD Setup
On CI, the setup is slightly different as the goals are:
- to resolve dependencies in CI/CD jobs in
- to publish new package versions in CD jobs for both internal and external dependencies
The steps described for the local setup are valid for the resolution on CI too. The interesting part here is how publishing is done. I will assume the usage of GitHub Actions.
1. Retrieving the Artifactory Token
The JFrog CLI can be used via the setup-jfrog-cli action to authenticate using the most appropriate method. You might want to wrap the action in a custom composable one exporting the token as the output of a step:
TOKEN=$(jf config export)
echo "::add-mask::$TOKEN"
echo "artifactory-token=$TOKEN">> "$GITHUB_OUTPUT"
2. Logging into the Registry
The CI job must log in to the local repository (swift-local
) to gain push permissions. The token retrieved in the previous step is used for this purpose.
swift package-registry login \
"https://packages.acme.com/artifactory/api/swift/swift-local" \
--token ${{ steps.get-token.outputs.artifactory-token }}
3. Publishing Packages
Swift Registry requires archives created with the swift package archive-source
command from the dependency folder. E.g.
swift package archive-source -o "Alamofire-5.10.2.zip"
We could avoid creating the archive and instead download it directly from GitHub releases.
curl -L -o Alamofire-5.10.1.zip \
https://github.com/Alamofire/Alamofire/archive/refs/tags/5.10.1.zip
Uploading the archive can then be done by using the JFrog CLI that needs customization via the setup-jfrog-cli action. If going down this route, the upload command would be:
jf rt upload Alamofire-5.10.1.zip \
https://packages.acme.com/artifactory/api/swift/swift-local/acme/Alamofire/Alamofire-5.10.1.zip
There is a specific structure to respect:
<REPOSITORY>/<SCOPE>/<NAME>/<NAME>-<VERSION>.zip
which is the last part of the above URL:
swift-local/acme/Alamofire/Alamofire-5.10.1.zip
Too bad that using the steps above causes a downstream problem with SPM not being able to resolve the dependencies in the registry. I tried extensively and couldn't find the reason why SPM wouldn't be happy with how the packages were published. I might have missed something but eventually I necessarily had to switch to use the publish
command.
Using the swift package-registry publish
command instead, doesn't present this issue hence it's the solution adopted in this workflow.
swift package-registry publish acme.Alamofire 5.10.1 \
--url https://packages.acme.com/artifactory/api/swift/swift-local \
--scratch-directory $(mktemp -d)
To verify the upload and indexing succeeded, check that the uploaded *.zip
artifact is available and that the .swift
exists (indication that the indexing has occurred). If the specific structure is not respected, the .swift
folder wouldn't be generated.
Consuming Packages from the Registry
Packages
The easiest and only documented way to consume a package from a registry is via a Package. In the Package.swift
file, declare dependencies using the .package(id:from:)
syntax to declare a registry-based dependency. The id
is a combination of the scope and the package name.
...
dependencies: [
.package(id: "acme.Alamofire", from: "5.10.1"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "Alamofire", package: "acme.Alamofire"),
]
),
...
]
)
Run swift package resolve
or simply build the Package in Xcode to pull the dependencies.
You might bump into transitive dependencies (i.e. dependencies listed in the Package.swift
files of the packages published on the registry) pointing to GitHub. In this case, it'd be great to instruct SPM to use the corresponding versions on the registry. The --replace-scm-with-registry
flag is designed to work for the entire dependency graph, including transitive dependencies.
The cornerstone of associating a registry-hosted package with its GitHub origin is the package-metadata.json
file. This file allows to provide essential metadata about the packages at the time of publishing (the --metadata-path
flag of the publish
command defaults to pacakge-metadata.json
).
Crucially, it includes a field to specify the source control repository URLs. When swift package resolve --replace-scm-with-registry
is executed, SPM queries the configured registry. The registry then uses the information from the package-metadata.json
to map the package identity to its corresponding GitHub URL, enabling a smooth and transparent resolution process.
The metadata file must conform to the JSON schema defined in SE-0391. It is recommended to include all URL variations (e.g., SSH, HTTPS) for the same repository. E.g.
{
"repositoryURLs": [
"https://github.com/Alamofire/Alamofire",
"https://github.com/Alamofire/Alamofire.git",
"git@github.com:Alamofire/Alamofire.git"
]
}
Printing the dependencies should confirm the source of the dependencies:
swift package show-dependencies --replace-scm-with-registry
Xcode
It's likely that you'll want to use the registry from Xcode projects for direct dependencies. If using the Tuist registry, it seems you would be able to leverage a Package Collection to add dependencies from the registry from the Xcode UI. Note that until Xcode 16.4, it's not possible to add registry dependencies directly in the Xcode UI, but if you use Tuist to generate your project (as you should), you can use the Package.registry
(introduced with https://github.com/tuist/tuist/pull/7225). E.g.
let project = Project(
...
packages: [
.registry(
identifier: "acme.Alamofire",
requirement: .exact(Version(stringLiteral: "5.10.1"))
)
],
...
)
Conclusions
We’ve found that using an in-house Swift registry can significantly reduce dependency resolution time by downloading only the required revision instead of the entire, potentially large, Git repository. This improvement benefits both CI pipelines and developers’ local environments. Additionally, registries help mitigate the risk of supply chain attacks.
As of this writing, Swift registries are not widely adopted, which is reflected in the limited number of platforms that support them. It's unclear whether adoption will grow, but when working with dependencies in large repositories, registries offer a more efficient and secure alternative to using XCFrameworks in production builds.