The fastest way to build and deploy server side Swift applications.
Swift Cloud is based on the premise that infrastructure should be defined along side your application, in the same language as your application. In our case, Swift. Define a new target, describe your infrastructure, and deploy it with a single command. There's no Dockerfiles, no Terrafrom configurations, no Node.js packages. Everything is defined in Swift and the complex configuration is handled behind the scenes, using modern architecture best practices.
AWS.WebServer(
"my-vapor-app",
targetName: "App",
concurrency: 1,
autoScaling: .init(
maximumConcurrency: 10,
metrics: [.cpu(50), .memory(50)]
)
)
The Swift Cloud package is powered by Pulumi. Specifically, the SDK vends Swift components that are compiled into Pulumi YAML files, and then the Pulumi CLI is used to deploy your application. You do not need a Pulumi account to use Swift Cloud, nor do you need to install Pulumi CLI on your machine. Everything is managed by the SDK and written to a .cloud
directory in your project.
In order to use Swift Cloud you need to have Docker installed on your machine. This is a short term limitation until Swift 6 where we will be able to natively cross-compile to Linux and other SDKs.
If you're on a Mac the easiest way to install Docker is OrbStack. Simply download OrbStack and run the installer.
Today, Swift Cloud only supports AWS. You will need to have an AWS account and AWS credentials loaded on your machine or in the typical environment variables.
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
If you're on a Mac the easiest way to manage your AWS credentials is Leapp.
You can also use the AWS CLI to configure your credentials:
aws configure
dependencies: [
.package(url: "https://github.com/swift-cloud/swift-cloud.git", branch: "main")
]
Swift Cloud works by declaring your infrastructure as Swift code. To do this you must create a new executable target in the same package as your application.
Start by defining a new executable target in your Package.swift
file:
targets: [
...
.executableTarget(
name: "Infra",
dependencies: [
.product(name: "Cloud", package: "swift-cloud")
]
)
]
Next, inside your Sources
directory create a new folder called Infra
.
Finally, add a new Swift file called Project.swift
:
import Cloud
@main
struct SwiftCloudDemo: Project {
func build() async throws -> Outputs {
let server = AWS.WebServer(
"my-vapor-web-server",
targetName: "App",
concurrency: 1,
autoScaling: .init(
maximumConcurrency: 10,
metrics: [.cpu(50), .memory(50)]
)
)
return Outputs([
"url": server.url,
])
}
}
swift run Infra deploy --stage production
Swift Cloud is invoked directly from your Swift package. You can run the following commands:
Deploy your infrastructure:
swift run Infra deploy --stage production
Remove all resources:
swift run Infra remove --stage development
Preview changes before deploying:
swift run Infra preview --stage development
Show the outputs of your deployment:
swift run Infra outputs --stage development
Cancel a deployment:
swift run Infra cancel --stage development
Swift Cloud allows you to deploy infrastructure across multiple cloud providers. In order to handle incremental changes to your infrastructure, Swift Cloud must store your underlying configuration in a durable location so it can be referenced anytime you run a deploy, whether from your local machine or a CI/CD pipeline.
We abstracted this concept into a HomeProvider
protocol, and allow you to decide where your configuration is stored. By default, Swift Cloud uses the AWS S3 service to store your configuration, but you can easily swap this out for any other provider that supports the HomeProvider
protocol.
For quick prototyping, you can use the Home.Local
provider, which stores your configuration in a local file. This is great for testing and development, but it's not recommended for production use.
import Cloud
@main
struct SwiftCloudDemo: Project {
// Override the default home provider with a local provider
let home = Home.Local()
func build() async throws -> Outputs {...}
}
This component creates a high performance web server using an application load balancer, auto-scaling group, and Fargate. Everything is fully managed and scales automatically based on your configuration.
let server = AWS.WebServer(
"my-vapor-web-server",
targetName: "App",
concurrency: 1,
autoScaling: .init(
maximumConcurrency: 10,
metrics: [.cpu(50), .memory(50)]
)
)
let lambda = AWS.Function(
"my-lambda-function",
targetName: "App",
url: .enabled(cors: true),
memory: 512,
timeout: .seconds(10)
)
This component creates a CDN that sits in front of your application. It can be used to cache your application assets, or to serve your application from a custom domain.
let cdn = AWS.CDN(
"my-cdn",
origins: .webServer(server)
)
You can also route traffic on different paths to different resoruces:
let cdn = AWS.CDN(
"my-cdn",
origins: [
.function(function, path: "/api/*"),
.webServer(server, path: "*")
]
)
And of course you can use a custom domain:
let cdn = AWS.CDN(
"my-cdn",
origins: .function(function),
domainName: .init("www.example.com")
)
let bucket = AWS.Bucket("my-s3-bucket")
let queue = AWS.Queue("my-sqs-queue")
// Subscribe a lambda function to the queue to process messages
queue.subscribe(
AWS.Function("my-lambda-function", targetName: "App")
)
let topic = AWS.Topic("my-sns-topic")
// Subscribe a lambda function to the topic to process events
topic.subscribe(
AWS.Function("my-lambda-function", targetName: "App")
)
let cron = AWS.Cron(
"my-cron-job",
schedule: .rate(.minutes(5))
)
// Invoke the function when the cron job runs
cron.invoke(
AWS.Function("my-lambda-function", targetName: "App")
)
The DomainName
construct manages a TLS certificate and the necessary validation, and can be linked to a WebServer
to provide a fully managed domain name.
Important: You must host your domain in a Route53 hosted zone in your AWS account to use this construct. In the future we will add support for domains hosted on other providers.
// Optionally pass a `zoneName` if the domain is not simply inferred from the `domainName`
let domainName = AWS.DomainName("www.example.com")
let server = AWS.WebServer(
"my-vapor-web-server",
targetName: "App",
domainName: domainName
)
return Outputs([
// Now server url will be `https://www.example.com`
"url": server.url
])
You can link resources together to provide the necessary permissions to access each other. This is more secure than sharing access key ids and secrets in environment variables.
For example you can link an S3 bucket to a Lambda function:
myFunction.link(bucket)
This allows the lambda function to access the bucket without needing to share access keys.
You can use linked resources in your server or function via environment variables in your application:
let bucketUrl = ProcessInfo.processInfo.environment["bucket:my-bucket:url"]
Here is a list of all the linked resources:
Resource | Environment Variable |
---|---|
AWS S3 Bucket | BUCKET_<NAME>_URL |
AWS S3 Bucket | BUCKET_<NAME>_HOSTNAME |
AWS S3 Bucket | BUCKET_<NAME>_NAME |
AWS SQS Queue | QUEUE_<NAME>_URL |
AWS SQS Queue | QUEUE_<NAME>_NAME |
AWS Lambda Function | FUNCTION_<NAME>_URL |
AWS Lambda Function | FUNCTION_<NAME>_NAME |