Packaging, build configuration and distribution
Build settings and xcconfig files, schemes and environment configuration, Swift packages as dependencies, TestFlight, App Store submission and server-side deployment.
Build settings and xcconfig
// Config/Shared.xcconfig
PRODUCT_BUNDLE_IDENTIFIER = com.example.app
MARKETING_VERSION = 2.4.0
CURRENT_PROJECT_VERSION = 1
SWIFT_VERSION = 6.0
SWIFT_STRICT_CONCURRENCY = complete
ENABLE_USER_SCRIPT_SANDBOXING = YES
// Config/Debug.xcconfig
#include "Shared.xcconfig"
BUNDLE_ID_SUFFIX = .debug
API_BASE_URL = https:/$()/staging.api.example.com
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG STAGING
// Config/Release.xcconfig
#include "Shared.xcconfig"
BUNDLE_ID_SUFFIX =
API_BASE_URL = https:/$()/api.example.com- Split configuration into a shared file and one per environment, then set each configuration's base in the project settings.
- Prefer compilation conditions and generated constants over reading the plist at runtime, so a missing value is a build error.
- In an xcconfig file,
//starts a comment, so a literal URL needs thehttps:/$()/workaround. - Settings you override in the project editor silently beat the xcconfig, which is the usual reason a change appears to do nothing.
Dependencies and schemes
| Source | Control | Best for |
|---|---|---|
| Swift Package, version range | A resolved file pins the graph | Most libraries |
| Swift Package, branch | Whatever the branch is now | A temporary fork |
| Local package by path | The source tree next to the app | Your own modules |
| XCFramework | A binary you build and host | Closed-source or non-Swift dependencies |
// a shared scheme committed to the repository is what CI runs
// App.xcodeproj/xcshareddata/xcschemes/App.xcscheme
let scheme = """
<Scheme LastUpgradeVersion="1600" version="1.7">
<TestAction buildConfiguration="Debug">
<Testables>
<TestableReference skipped="NO">
<BuildableReference BuildableIdentifier="primary"
BlueprintName="AppTests" BuildableName="AppTests.xctest"
ReferencedContainer="container:App.xcodeproj"/>
</TestableReference>
</Testables>
</TestAction>
</Scheme>
"""An unshared scheme lives only on the machine that created it. Commit the scheme, or CI will fail with a message about a missing destination that gives no hint about the real cause.
Distribution
# archive and export for the App Store
xcodebuild archive -project App.xcodeproj -scheme App \
-destination "generic/platform=iOS" -archivePath build/App.xcarchive
xcodebuild -exportArchive -archivePath build/App.xcarchive \
-exportOptionsPlist Config/ExportOptions.plist -exportPath build/ipa
# server-side Swift on Linux
swift build -c release --static-swift-stdlib
docker build -t registry.example.com/reports:1.4.0 .
docker push registry.example.com/reports:1.4.0⚠️
Use an App Store Connect API key rather than an Apple ID password for automated uploads, and keep it out of the repository. A leaked key can publish a build on your behalf, and the upload path is the last place you want shared credentials.
FAQ
How do I stop a staging build reaching production data?
Give each environment its own bundle identifier suffix and its own base URL from the xcconfig. Two installs, two sets of preferences, and no path for a staging token to be used against production.
What is the simplest way to run Swift on a server?
A Swift package produced with
swift build -c release into a small container. Static linking of the standard library keeps the image small and removes a runtime dependency from the base image.Related
Setting up Swift: Xcode, Swift Package Manager and Swift 6 Testing with XCTest and Swift Testing
Last refreshed 2026-09-18.