An Initial Hands-On with SwiftCloud
Deployment of server side Swift applications is one of the biggest challenges many developers face. It requires experience with different cloud providers, or understanding the intricacies of often complex services such as AWS. Besides that, working with Swift for Linux, while on a Mac, also requires a basic to intermediate understanding of Docker, the Dockerfile syntax, and writing scripts that transform the code into a binary running in a remote server.
An example of this complexity can be found in the official Swift implementation of the AWS Lambda Runtime, under the example deployment scripts directory. They encapsulate the logic for building, packaging and uploading the binary of a lambda function, and require a considerable effort to understand or modify.
SwiftCloud to the Rescue
This is exactly the goal that SwiftCloud, a new open source package by Andrew Barba, aims to solve. It’s agnostic to the type of Swift app (Vapor, Hummingbird, Lambda), and supports multiple different targets in a single package. You describe the infrastructure in a target alongside the app - all in Swift! - including the cloud providers and it does all the magic behind the scenes - building the targets with SPM, and uploading them, while creating the necessary resources if needed.
This article will cover an initial hands-on with it, where you can learn the basics of SwiftCloud and how to deploy two Lambda functions to AWS, including live examples! If you want to follow along and deploy your own functions, you should have an AWS account configured in your machine, and have Docker installed. Make sure you have AWS credentials set up and that both AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
environment variables are present in your shell.
SwiftCloud is still in early preview, so some things in the near future might change.
Currently, only AWS is supported, but Google Cloud, Cloudflare, Fly.io, and other providers are planned to be supported soon.
Adding SwiftCloud
Considering an existing app, SwiftCloud requires a target of its own on the same package. This article will use a sample project already containing two functions.
In the starter sample project, you can notice that there are two targets: HelloLambda and FetchGitHubRelease. The former is a function that responds with a JSON including information about the request, and the latter fetches the latest release of a GitHub repository.
By the end of this article, both will be live! To get started, open the Package.swift file in Xcode, VS Code, or your preferred editor. Under dependencies
, add SwiftCloud:
.package(url: "https://github.com/swift-cloud/swift-cloud.git", from: "0.16.0")
After that, add another target, under targets
. It will contain your project description, and depends exclusively on the AWSCloud target from SwiftCloud:
targets: [
// HelloLambda,
// FetchGitHubRelease,
.executableTarget(
name: "Infra",
dependencies: [
.product(name: "AWSCloud", package: "swift-cloud")
]
)
]
The name Infra per se is not important, and you can choose any other name that makes sense and represents a target for the infrastructure description. This name, however, will be the target name through which all the commands will be called in the sections below.
Describing a Project
Now you should notice that SPM encounters an error. It tries to create the new target, Infra, but it has no files. To solve that, create a new directory for the Infra target under Sources, and add a file named Project.swift. You can do it via UI, or via Terminal:
mkdir Sources/Infra
touch Sources/Infra/Project.swift
Now, open the Project.swift file, and add the following content:
import AWSCloud
@main
struct Project: AWSProject {
func build() async throws -> Outputs {
return [:]
}
}
Here, you declare the @main
entrypoint of this target, the Project struct, which conforms to the Cloud.Project
protocol. The only method the protocol requires is the build()
function, which returns Outputs
. More on that later, but for now, just return an empty dictionary.
Previewing Changes
To check that everything is set up correctly, you can run the Infra target. Although this can be done in the Xcode UI by editing the schemes, we’ll use Terminal for simplicity.
Whenever interacting with the SwiftCloud project, one parameter is required: stage. It represents the multiple possible environments of deployment, such as development, staging, or production. It can be any string that you can use to identify, and this article will use dev
for development, and prod
for production.
Run the command below. It might take a few moments for SPM to resolve packages and build the Infra target.
swift run Infra preview --stage dev
If you get an error with the message Cannot connect to the Docker daemon…, certify that Docker is running.
SwiftCloud will display a preview of what it will do behind the scenes. In this case, as the project is empty, only one resource will be created: it’s the “shell” app, without functions or servers. Notice how it contains dev
as suffix - indicating that every stage stays isolated from the others.
Adding Resources
With the build()
function in place, the last missing step before deploying is to add the AWS.Function
descriptions.
1st Lambda
Start with the first one, in the top of the build function:
let helloFunction = AWS.Function(
"HelloLambda", // 1
targetName: "HelloLambda", // 2
url: .enabled(), // 3
memory: 256, // 4
timeout: .seconds(10), // 5
environment: ["stage": Context.current.stage] // 6
)
Creating a function requires a few parameters:
- A name, which together with the stage, can be used to identify the function in the AWS Console or CLI
- The name of the target which SPM will build and associate the function with
- If you want to call the function via HTTP, pass
.enabled()
in the URL parameter, but if you’re planning to use this function with other AWS triggers, disable it - For a simple Swift AWS Lambda, 256 MB should be enough
- Define a timeout - 10 seconds in this case is also enough
- The first function accesses an environment variable named
"stage"
. Get the stage from the current context, and pass it in theenvironment
dictionary.
2nd Lambda
The second lambda function definition is very similar, except that it uses a different name, a different target, and doesn’t require any environment variable. Add the following code below the first definition:
let gitHubReleaseFunction = AWS.Function(
"FetchGitHubRelease",
targetName: "FetchGitHubRelease",
url: .enabled(),
memory: 256,
timeout: .seconds(10)
)
Finally, return the outputs you’re interested in. The Outputs
type conforms to the ExpressibleByDictionaryLiteral
, so you can return a dictionary whose keys are strings, and the values will be the function URLs:
return [
"HelloLambda": helloFunction.url,
"FetchGitHubRelease": gitHubReleaseFunction.url
]
To test that everything is in its place, run again the preview command:
swift run Infra preview --stage dev

Now, notice in the output all the work that SwiftCloud will do. It will create in total 12 resources, which include the necessary roles, policies, images and the lambda functions. How cool is that?
Deploying
To deploy these changes, you guessed it right - use the deploy
command:
swift run Infra deploy --stage dev
Once the command finishes, you can find at the bottom of the terminal output, the URL for each function deployed, as requested by the Outputs
value you returned:

When opening the AWS Console and looking for the Lambda functions, you should see them there:

Copy the URL for the first function, HelloLambda, and run a simple curl
:
curl <function-url>
You can also press the button below and see the result live:
If it looks good, you can now deploy to production, using the same deploy command, but passing now --prod
as the stage argument:
swift run Infra deploy --stage prod
Now, in the AWS Console, you should see all 4 functions - 2 for each stage:

You can test here the second function, that fetches the latest release for a given GitHub repository.
Removing Deploys
In many situations you might want to remove a deploy. For our exercise, we will remove the development stage, after having deployed to production:
swift run Infra remove --stage dev
As the output shows, the resources associated with the development stage were removed.
Additional Resources
Here we only covered Lambda functions. SwiftCloud provides a swifty way to describe more AWS resources, such as a Queues, Cron jobs, buckets. When combined, they can be really powerful and compose sophisticated server apps. And of course, you can also use the WebServer component to deploy any existing Hummingbird or Vapor application.
Explore Further
Thanks for reading this article and learning how to deploy server apps with SwiftCloud.
If you have any questions, feel free to drop a comment or questions at X or Mastodon, or also get in touch with Andrew Barba, the creator of SwiftCloud.
Here are some remarks and considerations:
- As mentioned in the beginning, SwiftCloud is still in an early preview, so some breaking changes might be introduced, and other providers besides AWS are still to be supported
- Make sure you have enough disk space in your Mac. Working with Docker, pulling Swift images and building new ones takes a lot of space, and you will encounter annoying and frustrating errors
- SwiftCloud will use the current Swift version on your environment. If you’re using a Beta version of Xcode or macOS, you might face unexpected errors