Warning
This package is under active development. APIs are subject to change without notice.
A Swift package for managing local containers in tests — define containers as types, start them automatically with Swift Testing, and tear them down when your suite completes.
- Type-safe container definitions via the
ContainerKeyprotocol - Swift Testing integration with
@Suite(.containers(...))traits - Cross-platform runtime auto-selection — Docker/Podman on Linux, Apple Containerization on macOS 26+
- Built-in LocalStack support with CDK and CloudFormation setup steps
- Flexible wait strategies — port readiness, health check, log matching, fixed delay, or custom logic
- Shared containers across test suites with
@Suite(.sharedContainers(...))for performance
Conform to ContainerKey to declare a reusable container definition:
import LocalContainers
struct MyPostgres: ContainerKey {
static let spec = ContainerSpec(
ContainerConfiguration(
image: "postgres:16",
ports: [PortMapping(containerPort: 5432)],
environment: ["POSTGRES_PASSWORD": "test"],
healthCheck: HealthCheckConfig(
test: ["CMD", "pg_isready"],
interval: .seconds(10),
timeout: .seconds(5),
retries: 3
),
waitStrategy: .healthCheck
)
)
}Attach containers to a test suite with the .containers(...) trait. Access running containers through ContainerTestContext:
import Testing
import ContainerTestSupport
@Suite(.containers(MyPostgres.self))
struct DatabaseTests {
@Test func connectionWorks() async throws {
let ctx = try #require(ContainerTestContext.current)
let postgres = try ctx[MyPostgres.self]
let hostPort = try postgres.mappedPort(5432)
// Connect to localhost:\(hostPort) ...
}
}Use the built-in LocalStackContainer builder and compose setup steps manually. CDKSetup runs CDK at test time and deploys to LocalStack. Two operating modes depending on whether your stack uses CDK assets.
autoBootstrap: false (default) — assetless stacks, fast path. Runs cdk synth locally and hands the template to CloudFormationSetup, which transparently stubs the /cdk-bootstrap/hnb659fds/version SSM parameter before CreateStack. Use this for any stack containing only "inline" resources — DynamoDB tables, SQS queues, SNS topics, Step Functions, S3 buckets, IAM roles, etc. No real bootstrap needed.
import LocalContainers
import LocalStack
struct MyLocalStack: ContainerKey {
static let spec = ContainerSpec(
LocalStackContainer(
services: ["s3", "dynamodb", "sqs"]
).configuration(),
setups: [
CDKSetup(
cdkAppPath: "infra",
stackName: "MyStack"
)
]
)
}autoBootstrap: true — asset-bearing stacks. Delegates to aws-cdk-local (the cdklocal CLI), which wraps the regular CDK CLI and routes every AWS API call at LocalStack. Runs cdklocal bootstrap to create a real CDKToolkit stack inside LocalStack, then cdklocal deploy to upload assets and create the application stack. Use this when your stack uses lambda.Code.fromAsset(...), ecs.ContainerImage.fromAsset(...), bundled CloudFormation init scripts, or any other asset type.
struct MyStackWithLambda: ContainerKey {
static let spec = ContainerSpec(
LocalStackContainer().configuration(), // default services
setups: [
CDKSetup(
cdkAppPath: "infra",
stackName: "MyStack",
autoBootstrap: true
)
]
)
}Requires aws-cdk-local in your CDK app's devDependencies. Adds ~30 seconds per test suite for the cdklocal bootstrap step.
Important
aws-cdk must be pinned to 2.1113.0 or earlier in your CDK app's package.json until aws-cdk-local ships a fix for localstack/aws-cdk-local#126.
Versions of aws-cdk from 2.1114.0 onward removed the internal module exports (lib/cdk-toolkit, lib/serialize, lib/api, etc.) that cdklocal monkey-patches to route CDK calls at LocalStack. With an unpinned aws-cdk, cdklocal bootstrap fails immediately with ERR_PACKAGE_PATH_NOT_EXPORTED. The aws-cdk team has introduced an official replacement, @aws-cdk/toolkit-lib, and cdklocal is in the process of migrating — once it does, this pin can be removed.
This constraint only applies to the autoBootstrap: true path. Projects using autoBootstrap: false (the default — SSM-stub fast path) or using the declarative cdkapps[] flow for assetless stacks are not affected and can freely consume any aws-cdk version.
Example package.json snippet:
{
"devDependencies": {
"aws-cdk": "2.1113.0",
"aws-cdk-local": "^2.19.0"
}
}For projects that want strongly-typed access to stack outputs and have the CDK synth happen at build time (rather than test time), declare your CloudFormation templates and CDK apps in .local-containers/codegen.json at the package root:
{
"templates": [
{
"source": "Resources/my-infra.json",
"structName": "MyInfraOutputs"
}
],
"cdkapps": [
{
"source": "Resources/my-cdk-app",
"stackName": "MyStack",
"structName": "MyStackOutputs"
}
]
}The ContainerCodeGen build plugin reads the manifest, runs cdk synth for each cdkapps[] entry during swift build, and generates a StackOutputs-conforming struct with typed accessors for every output declared in the template's Outputs section. Use the generated struct in your test suite via @Containers + @LocalStackContainer:
import ContainerMacrosLib
import ContainerTestSupport
import Testing
@Containers
struct MyContainers {
@LocalStackContainer(stackName: "my-stack")
var infra: MyStackOutputs
}
@Suite(MyContainers.containerTrait, .tags(.integration))
struct InfraTests {
let containers = MyContainers()
@Test func deployedStackIsUsable() async throws {
let infra = containers.infra
// infra.bucketName, infra.queueUrl, ... are strongly typed
// from the template's Outputs section
print(infra.awsEndpoint, infra.bucketName)
}
}SwiftPM's build-plugin sandbox denies network access, so the build plugin cannot run npm install itself. Run the bootstrap command plugin once after a fresh checkout (or whenever you add a new cdkapps[] entry):
swift package --allow-network-connections all \
--allow-writing-to-package-directory bootstrapThis iterates every cdkapps[] entry in the manifest and runs npm install in each CDK app directory. After it completes, swift build and swift test work normally with the sandbox fully intact.
Your CI pipeline needs two things:
- Node.js available on PATH (GitHub-hosted
ubuntu-*andmacos-*runners include it by default). - A
swift package bootstrapstep before anyswift build/swift testcommand. Without it, the build will fail when the CDK codegen plugin cannot findnode_modules/.bin/cdk.
Example GitHub Actions snippet:
- uses: actions/checkout@v6
- name: Bootstrap
run: swift package --allow-network-connections all --allow-writing-to-package-directory bootstrap
- name: Run tests
run: swift testThis requirement only applies to projects that declare cdkapps[] entries. Projects using templates[] for handwritten CloudFormation — or no manifest at all — need no bootstrap step.
- Swift 6.1+
- macOS 15+ or Linux
- Docker, Podman, or Apple Containerization (macOS 26+)
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/tachyonics/swift-local-containers.git", from: "0.1.0")
]Then add the libraries you need to your test target:
.testTarget(
name: "MyAppTests",
dependencies: [
.product(name: "LocalContainers", package: "swift-local-containers"),
.product(name: "ContainerTestSupport", package: "swift-local-containers"),
// Optional:
// .product(name: "LocalStack", package: "swift-local-containers"),
// .product(name: "DockerRuntime", package: "swift-local-containers"),
// .product(name: "PlatformRuntime", package: "swift-local-containers"),
]
)This project is licensed under the Apache License, Version 2.0.