Setting up a consistent, cross-platform Swift development environment can be tricky, especially when you need to interact with AWS services. This guide will walk you through creating a powerful, containerized setup using Visual Studio Code, Dev Containers, and LocalStack.
You'll get a fully functional Swift environment that runs inside Docker, complete with debugging, code completion, and a local AWS cloud stack for all your testing needs. Best of all? It works identically on macOS, Linux, and Windows.
LocalStack is a fully functional local AWS cloud stack that allows you to test your cloud applications locally without connecting to the real AWS cloud. It provides a set of APIs that mimic the behavior of AWS services, making it easy to develop and test your applications in a safe and controlled environment.
Before we start, make sure you have these tools installed on your system:
- Docker
- Visual Studio Code
- Visual Studio Code Dev Containers Extension
- Docker Compose V2
- It's also helpful to review the official guide on configuring VS Code for Swift and install Swift extension.
Let's build our environment from the ground up.
Open your terminal, create a new directory for your project, and navigate into it.
mkdir SwiftInDevContainer
cd SwiftInDevContainer
Launch VS Code and open the SwiftInDevContainer
folder you just created.
Open the VS Code integrated terminal and use the Swift Package Manager to create a new executable project.
swift package init --type executable --name SwiftInDevContainer
For Swift code completion and language features to work, we need to generate a config file.
- Open the command palette: Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows/Linux).
- Type and select "Swift: Generate SourceKit-LSP Configuration...".
This will create a sourcekit-lsp/config.json
file. It should look like this:
{
"$schema": "https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/release/6.2/config.schema.json"
}
Let's make sure you can build and run locally first. Try running the debugger. The Swift extension should automatically generate a .vscode/launch.json
file for you.
Note: This initial launch.json
will contain paths specific to your local machine (like /Users/yourname/Documents...
). This is expected! We'll replace this file once we're inside the container.
Now, let's tell VS Code how to build our container.
- Open the command palette (Cmd+Shift+P / Ctrl+Shift+P).
- Type and select "Dev Containers: Add Dev Container Configuration Files...".
- Choose "Add configuration to workspace".
- Choose "Docker outside of Docker Compose".
- Select "latest".
- Select the default options
- When prompted to add features, select "AWS CLI".
- You can also select
.github/dependabot.yml
if you plan to use it.
This will create a .devcontainer
folder with a devcontainer.json
and docker-compose.yml
file.
We need to customize this file to install the Swift extension inside the container and configure the LLDB debugger.
Open .devcontainer/devcontainer.json
and add the "customizations"
block shown below. This will automatically install the Swift extension and tell the debugger where to find the correct library inside the container.
Next, open .devcontainer/docker-compose.yml
. We need to:
- Add the LocalStack service.
- Make our
app
service depend on LocalStack. - Add environment variables so our Swift app can find LocalStack.
- Enable
ptrace
for the debugger to work.
Your final docker-compose.yml
should look like this:
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
# Forwards the local Docker socket to the container.
- /var/run/docker.sock:/var/run/docker-host.sock
# Update this to wherever you want VS Code to mount the folder of your project
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
entrypoint: /usr/local/share/docker-init.sh
# Add dependency on localstack
depends_on:
- localstack
# Add environment variables for AWS SDK
environment:
- LOCALSTACK_ENDPOINT=http://localstack:4566
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_REGION=us-east-1
command: sleep infinity
# Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust.
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
# Add the localstack service
localstack:
image: localstack/localstack
In the same .devcontainer
folder, change the Dockerfile
content. This file will define our main app
container.
We'll use an official Swift image and add make
, which is useful for utility scripts.
FROM swift:6.2.0
# Add make installation
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends make
Now for the magic.
- Open the command palette (Cmd+Shift+P / Ctrl+Shift+P).
- Type and select "Dev Containers: Rebuild and Reopen in Container".
VS Code will now build your Dockerfile
, start the docker-compose
services (your Swift app and LocalStack), and reload the window, connecting you inside the app
container. You'll know it worked when the bottom-left corner of VS Code shows "Dev Container: ...".
Remember that old launch.json
from Step 5? It's pointing to your local machine's paths, which don't exist inside the container. Let's fix it.
- You can delete the old
.vscode/launch.json
file if you want, or just let this next step overwrite it. - Select the
Package.swift
file in the Explorer to make it the active file. - Open the command palette (Cmd+Shift+P / Ctrl+Shift+P).
- Type and select "Swift: Generate Launch Configuration".
This will create a new launch.json
with the correct container paths (e.g., /workspaces/SwiftInDevContainer/.build/debug/SwiftInDevContainer
).
Now, you can start Debugging. The project will build inside the container, and the "Hello, world!" output will appear in your debug console.
Let's prove our container can talk to the LocalStack container. We'll add Soto, the Swift SDK for AWS, to query DynamoDB.
First, update your Package.swift
to include soto-dynamodb
:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "SwiftInDevContainer",
dependencies: [
.package(url: "https://github.com/soto-project/soto.git", from: "7.0.0"),
],
targets: [
.executableTarget(
name: "SwiftInDevContainer",
dependencies: [
.product(name: "SotoDynamoDB", package: "soto"),
]
),
]
)
Next, update Sources/SwiftInDevContainer/SwiftInDevContainer.swift
to list DynamoDB tables:
import SotoDynamoDB
@main
struct SwiftInDevContainer {
static func main() async throws {
// Use the environment variables we set in docker-compose.yml
let awsClient = AWSClient()
// Point the client at our LocalStack container
let dynamoDB = DynamoDB(client: awsClient, endpoint: "http://localstack:4566")
print("List tables")
let result = try await dynamoDB.listTables()
print("result: \(result)")
try await awsClient.shutdown()
}
}
Run the debugger again. VS Code will resolve the new package and run the code. You should see this output, as we haven't created any tables yet:
List tables
result: ListTablesOutput(lastEvaluatedTableName: nil, tableNames: Optional([]))
Let's create a table using the AWS CLI (which we installed in our container) and make
.
Create a Makefile
in the root of your project:
localstack_create_dynamo_db_table:
aws --endpoint-url=http://localstack:4566 dynamodb create-table \
--table-name Breeze \
--attribute-definitions AttributeName=itemKey,AttributeType=S \
--key-schema AttributeName=itemKey,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region us-east-1
Now, in the VS Code terminal (which is inside the container), run the command:
make localstack_create_dynamo_db_table
This tells the AWS CLI to connect to our localstack
service and create a table named "Breeze".
Run your Swift app one more time (F5). This time, the output will show your new table!
List tables
result: ListTablesOutput(lastEvaluatedTableName: nil, tableNames: Optional(["Breeze"]))
Congratulations! You now have a fully containerized, cross-platform Swift development environment. You can build, debug, and test your Swift applications that interact with AWS services, all without needing a real AWS account or installing Swift on your host machine.
If you're interested in taking this further and building Swift serverless applications with AWS Lambda and DynamoDB, check out the Breeze project.
Happy coding!
Don't forget to check out my blog: www.andrea-scuderi.com