Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .spi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ version: 1
builder:
configs:
- documentation_targets:
- containertool
- swift-container-plugin
2 changes: 1 addition & 1 deletion Examples/HelloWorldHummingbird/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let package = Package(
platforms: [.macOS(.v14)],
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.1.0"),
.package(url: "https://github.com/apple/swift-container-plugin", from: "0.5.0"),
.package(url: "https://github.com/apple/swift-container-plugin", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
],
targets: [
Expand Down
2 changes: 1 addition & 1 deletion Examples/HelloWorldVapor/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let package = Package(
platforms: [.macOS(.v13)],
dependencies: [
.package(url: "https://github.com/vapor/vapor", from: "4.102.0"),
.package(url: "https://github.com/apple/swift-container-plugin", from: "0.5.0"),
.package(url: "https://github.com/apple/swift-container-plugin", from: "1.0.0"),
],
targets: [.executableTarget(name: "hello-world", dependencies: [.product(name: "Vapor", package: "vapor")])]
)
2 changes: 1 addition & 1 deletion Examples/HelloWorldWithResources/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let package = Package(
platforms: [.macOS(.v14)],
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.1.0"),
.package(url: "https://github.com/apple/swift-container-plugin", from: "0.5.0"),
.package(url: "https://github.com/apple/swift-container-plugin", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
],
targets: [
Expand Down
31 changes: 21 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Swift Container Plugin

[![](https://img.shields.io/badge/docc-read_documentation-blue)](https://swiftpackageindex.com/apple/swift-container-plugin/documentation/containerimagebuilderplugin)
[![](https://img.shields.io/badge/docc-read_documentation-blue)](https://swiftpackageindex.com/apple/swift-container-plugin/main/documentation/swift-container-plugin)
[![](https://img.shields.io/github/v/release/apple/swift-container-plugin?include_prereleases)](https://github.com/apple/swift-container-plugin/releases)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fapple%2Fswift-container-plugin%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/apple/swift-container-plugin)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fapple%2Fswift-container-plugin%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/apple/swift-container-plugin)
Expand All @@ -12,20 +12,27 @@ Build and publish container images using Swift Package Manager.

Container images are the standard way to package cloud software today. Once you have packaged your server in a container image, you can deploy it on any container-based public or private cloud service, or run it locally using a desktop container runtime.

Swift Container Plugin makes it easy to build container images for servers written in Swift, using Swift Package Manager.
Use Swift Container Plugin to build and publish container images for your Swift services in one streamlined workflow with Swift Package Manager.

Find out more and see it in action:
![Swift Container Plugin flow diagram](Sources/swift-container-plugin/Documentation.docc/_Resources/swift-container-plugin-flow-diagram.png)

* [How to put Swift in a box](https://fosdem.org/2025/schedule/event/fosdem-2025-5116-how-to-put-swift-in-a-box-building-container-images-with-swift-container-plugin/) at [FOSDEM 2025](https://fosdem.org/2025/schedule/track/swift/).
* [Swift to the cloud in a single step](https://www.youtube.com/watch?v=9AaINsCfZzw) at [ServerSide.Swift 2024](https://www.serversideswift.info/2024/speakers/euan-harris/).
1. [Add the plugin to your project's dependencies](https://swiftpackageindex.com/apple/swift-container-plugin/main/documentation/swift-container-plugin/adding-the-plugin-to-your-project) in `Package.swift`.
2. [Build and package your service](https://swiftpackageindex.com/apple/swift-container-plugin/main/documentation/swift-container-plugin/build) using Swift Package Manager.
- If you are building on macOS, [use a Swift SDK](https://swiftpackageindex.com/apple/swift-container-plugin/main/documentation/swift-container-plugin/requirements) to cross-compile a Linux executable.
- If you are building on Linux, use your native Swift compiler to build a Linux executable. If you have special requirements such as building a static executable, or cross-compiling to a different processor architecture, use a suitable Swift SDK.
3. The plugin automatically packages your executable in a container image and publishes it to your chosen container registry.
4. [Run your container image](https://swiftpackageindex.com/apple/swift-container-plugin/main/documentation/swift-container-plugin/run) on any container-based platform.

Find full details [in the documentation](https://swiftpackageindex.com/apple/swift-container-plugin/main/documentation/swift-container-plugin).

## Usage

Swift Container Plugin can package any executable product defined in `Package.swift` in a container image and publish it to a container registry.

### Build and publish a container image

After adding the plugin to your project, you can build and publish a container image in one step:
After adding the plugin to your project, you can build and publish a container image in one step.
Here is how to build the [HelloWorld example](https://github.com/apple/swift-container-plugin/tree/main/Examples/HelloWorldHummingbird) as a static executable for Linux running on the `x86_64` architecture:

```
% swift package --swift-sdk x86_64-swift-linux-musl \
Expand All @@ -52,7 +59,7 @@ registry.example.com/myservice@sha256:a3f75d0932d052dd9d448a1c9040b16f9f2c2ed919

### Run the image

You can deploy your service in the cloud, or use a standards-compliant container runtime such as `podman` to run it locally:
Deploy your service in the cloud, or use a standards-compliant container runtime to run it locally:

```
% podman run -p 8080:8080 registry.example.com/myservice@sha256:a3f75d0932d052dd9d448a1c9040b16f9f2c2ed9190317147dee95a218faf1df
Expand All @@ -67,8 +74,12 @@ Trying to pull registry.example.com/myservice@sha256:a3f75d0932d052dd9d448a1c904
* On macOS you must install a cross-compilation Swift SDK, such as the [Swift Static Linux SDK](https://www.swift.org/documentation/articles/static-linux-getting-started.html), in order to build executables which can run on Linux-based cloud infrastructure.
* A container runtime is not required to build an image, but one must be available wherever the image is to be run.

## Getting Started
### Find out more
* Learn more about setting up your project in the [plugin documentation](https://swiftpackageindex.com/apple/swift-container-plugin/main/documentation/swift-container-plugin).

* Take a look at [more examples](https://github.com/apple/swift-container-plugin/tree/main/Examples).

Learn more about setting up your project in the [plugin documentation](https://swiftpackageindex.com/apple/swift-container-plugin/documentation/containerimagebuilderplugin).
* Watch some talks:

Take a look at the [Examples](Examples).
* [How to put Swift in a box](https://fosdem.org/2025/schedule/event/fosdem-2025-5116-how-to-put-swift-in-a-box-building-container-images-with-swift-container-plugin/) at [FOSDEM 2025](https://fosdem.org/2025/schedule/track/swift/).
* [Swift to the cloud in a single step](https://www.youtube.com/watch?v=9AaINsCfZzw) at [ServerSide.Swift 2024](https://www.serversideswift.info/2024/speakers/euan-harris/).
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,30 @@
extension RegistryClient {
/// Get an image configuration record from the registry.
/// - Parameters:
/// - repository: Name of the repository containing the record.
/// - image: Reference to the image containing the record.
/// - digest: Digest of the record.
/// - Returns: The image confguration record stored in `repository` with digest `digest`.
/// - Throws: If the blob cannot be decoded as an `ImageConfiguration`.
///
/// Image configuration records are stored as blobs in the registry. This function retrieves the requested blob and tries to decode it as a configuration record.
public func getImageConfiguration(repository: String, digest: String) async throws -> ImageConfiguration {
try await getBlob(repository: repository, digest: digest)
public func getImageConfiguration(forImage image: ImageReference, digest: String) async throws -> ImageConfiguration
{
try await getBlob(repository: image.repository, digest: digest)
}

/// Upload an image configuration record to the registry.
/// - Parameters:
/// - repository: Name of the repository in which to store the record.
/// - image: Reference to the image associated with the record.
/// - configuration: An image configuration record
/// - Returns: An `ContentDescriptor` referring to the blob stored in the registry.
/// - Throws: If the blob upload fails.
///
/// Image configuration records are stored as blobs in the registry. This function encodes the provided configuration record and stores it as a blob in the registry.
public func putImageConfiguration(repository: String, configuration: ImageConfiguration) async throws
public func putImageConfiguration(forImage image: ImageReference, configuration: ImageConfiguration) async throws
-> ContentDescriptor
{
try await putBlob(
repository: repository,
repository: image.repository,
mediaType: "application/vnd.oci.image.config.v1+json",
data: configuration
)
Expand Down
8 changes: 4 additions & 4 deletions Sources/containertool/Extensions/RegistryClient+Layers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ import struct Foundation.Data
import ContainerRegistry

extension RegistryClient {
func getImageManifest(repository: String, reference: String, architecture: String) async throws -> ImageManifest {
func getImageManifest(forImage image: ImageReference, architecture: String) async throws -> ImageManifest {
// We pushed the amd64 tag but it points to a single-architecture index, not directly to a manifest
// if we get an index we should get a manifest, otherwise we might get a manifest directly

do {
// Try to retrieve a manifest. If the object with this reference is actually an index, the content-type will not match and
// an error will be thrown.
return try await getManifest(repository: repository, reference: reference)
return try await getManifest(repository: image.repository, reference: image.reference)
} catch {
// Try again, treating the top level object as an index.
// This could be more efficient if the exception thrown by getManfiest() included the data it was unable to parse
let index = try await getIndex(repository: repository, reference: reference)
let index = try await getIndex(repository: image.repository, reference: image.reference)
guard let manifest = index.manifests.first(where: { $0.platform?.architecture == architecture }) else {
throw "Could not find a suitable base image for \(architecture)"
}
// The index should not point to another index; if it does, this call will throw a final error to be handled by the caller.
return try await getManifest(repository: repository, reference: manifest.digest)
return try await getManifest(repository: image.repository, reference: manifest.digest)
}
}

Expand Down
Loading