This example demonstrates a Swift Lambda function that uses Swift Service Lifecycle to manage a PostgreSQL connection. The function connects to an RDS PostgreSQL database in private subnets and queries user data.
- Swift Lambda Function: A network isolated Lambda function that Uses Swift ServiceLifecycle to manage PostgreSQL client lifecycle
- PostgreSQL on Amazon RDS: Database instance in private subnets with SSL/TLS encryption
- HTTP API Gateway: HTTP endpoint to invoke the Lambda function
- VPC: Custom VPC with private subnets only for complete network isolation
- Security: SSL/TLS connections with RDS root certificate verification, secure networking with security groups
- Timeout Handling: 3-second timeout mechanism to prevent database connection freeze
- Secrets Manager: Secure credential storage and management
For detailed infrastructure and cost information, see INFRASTRUCTURE.md.
The Lambda function demonstrates several key concepts:
-
ServiceLifecycle Integration: The PostgreSQL client and Lambda runtime are managed together using ServiceLifecycle, ensuring proper initialization and cleanup.
-
SSL/TLS Security: Connections to RDS use SSL/TLS with full certificate verification using region-specific RDS root certificates.
-
Timeout Protection: A custom timeout mechanism prevents the function from freezing when the database is unreachable (addresses PostgresNIO issue #489).
-
Structured Response: Returns a JSON array of
Userobjects, making it suitable for API integration. -
Error Handling: Comprehensive error handling for database connections, queries, and certificate loading.
- Swift 6.x toolchain
- Docker (for building Lambda functions)
- AWS CLI configured with appropriate permissions
- SAM CLI installed
In the context of this demo, the Lambda function creates the table and populates it with data at first run.
The Lambda function expects a users table with the following structure and returns results as User objects:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL
);
-- Insert some sample data
INSERT INTO users (username) VALUES ('alice'), ('bob'), ('charlie');The Swift User model:
struct User: Codable {
let id: Int
let username: String
}The Lambda function uses the following environment variables for database connection:
DB_HOST: Database hostname (set by CloudFormation from RDS endpoint)DB_USER: Database username (retrieved from Secrets Manager)DB_PASSWORD: Database password (retrieved from Secrets Manager)DB_NAME: Database name (defaults to "test")AWS_REGION: AWS region for selecting the correct RDS root certificate
./deploy.sh-
Build the Lambda function:
swift package archive --allow-network-connections docker
-
Deploy with SAM:
sam deploy
After deployment, get the database and API Gateway connection details:
aws cloudformation describe-stacks \
--stack-name servicelifecycle-stack \
--query 'Stacks[0].Outputs'The output will include:
- DatabaseEndpoint: Hostname to connect to
- DatabasePort: Port number (5432)
- DatabaseName: Database name
- DatabaseUsername: Username
- DatabasePassword: Password
- DatabaseConnectionString: Complete connection string
The database is deployed in private subnets and is not directly accessible from the internet. This follows AWS security best practices.
To connect to the database, you would need to create an Amazon EC2 instance in a public subnet (which you'd need to add to the VPC) or use AWS Systems Manager Session Manager for secure access to an EC2 instance in a private subnet. The current template uses a private-only architecture for maximum security.
You can access the database connection details in the output of the SAM template:
# Get the connection details from CloudFormation outputs
DB_HOST=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabaseEndpoint`].OutputValue' --output text)
DB_PORT=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabasePort`].OutputValue' --output text)
DB_NAME=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabaseName`].OutputValue' --output text)
# Get the database password from Secrets Manager
SECRET_ARN=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabaseSecretArn`].OutputValue' --output text)
DB_USERNAME=$(aws secretsmanager get-secret-value --secret-id "$SECRET_ARN" --query 'SecretString' --output text | jq -r '.username')
DB_PASSWORD=$(aws secretsmanager get-secret-value --secret-id "$SECRET_ARN" --query 'SecretString' --output text | jq -r '.password')
# Connect with psql on Amazon EC2
psql -h "$DB_HOST:$DB_PORT" -U "$DB_USER" -d "$DB_NAME"Get the API Gateway endpoint and test the function:
# Get the API endpoint
API_ENDPOINT=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`APIGatewayEndpoint`].OutputValue' --output text)
# Test the function
curl "$API_ENDPOINT"The function will:
- Connect to the PostgreSQL database using SSL/TLS with RDS root certificate verification
- Query the
userstable with a 3-second timeout to prevent freezing - Log the results for each user found
- Return a JSON array of
Userobjects withidandusernamefields
Example response:
[
{"id": 1, "username": "alice"},
{"id": 2, "username": "bob"},
{"id": 3, "username": "charlie"}
]Check the Lambda function logs:
sam logs -n ServiceLifecycleLambda --stack-name servicelifecycle-stack --tail✅ Security Best Practices Implemented:
This example follows AWS security best practices:
- Private Database: Database is deployed in private subnets with no internet access
- Complete Network Isolation: Private subnets only with no internet connectivity
- Security Groups: Restrictive security groups following least privilege principle
- Secrets Management: Database credentials stored in AWS Secrets Manager
- Encryption: SSL/TLS for database connections with certificate verification
- Minimal Attack Surface: No public subnets or internet gateways
The infrastructure implements secure networking patterns suitable for production workloads.
The template is optimized for cost:
db.t3.microinstance (eligible for free tier)- Minimal storage allocation (20GB)
- No Multi-AZ deployment
- No automated backups
- No NAT Gateway or Internet Gateway
- Private-only architecture
Estimated cost: ~$16.62/month (or ~$0.45/month with RDS Free Tier)
For detailed cost breakdown, see INFRASTRUCTURE.md.
To delete all resources:
sam delete --stack-name servicelifecycle-stackThis example includes RDS root certificates for secure SSL/TLS connections. Currently supported regions:
us-east-1: US East (N. Virginia)eu-central-1: Europe (Frankfurt)
To add support for additional regions:
- Download the appropriate root certificate from AWS RDS SSL documentation
- Create a new Swift file in
Sources/RDSCertificates/with the certificate PEM data - Add the region mapping to
rootRDSCertificatesdictionary inRootRDSCert.swift
when deploying with SAM and the template.yaml file included in this example, there shouldn't be any error. However, when you try to create such infarstructure on your own or using different infrastructure as code (IaC) tools, it's likely to iterate before getting everything configured. We compiled a couple of the most common configuration errors and their solution:
- Check security groups allow traffic on port 5432 between Lambda and RDS security groups
- Verify both Lambda and RDS are deployed in the same private subnets
- Verify database credentials are correctly retrieved from Secrets Manager and that the Lambda execution policies have permissions to read the secret
- Ensure the RDS instance is running and healthy
The PostgreSQL client may freeze if the database is unreachable. This example implements a 3-second timeout mechanism to prevent this issue. If the connection or query takes longer than 3 seconds, the function will timeout and return an empty array. Ensure:
- Database is running and accessible
- Security groups are properly configured
- Network connectivity is available
- SSL certificates are properly configured for your AWS region
Ensure you have:
- Swift 6.x toolchain installed
- Docker running
- Proper network connectivity for downloading dependencies
- All required dependencies: PostgresNIO, AWSLambdaRuntime, and ServiceLifecycle
template.yaml: SAM template defining all AWS resourcesINFRASTRUCTURE.md: Detailed infrastructure architecture documentationsamconfig.toml: SAM configuration filedeploy.sh: Deployment scriptSources/Lambda.swift: Swift Lambda function code with ServiceLifecycle integrationSources/Timeout.swift: Timeout utility to prevent database connection freezesSources/RDSCertificates/RootRDSCert.swift: RDS root certificate managementSources/RDSCertificates/us-east-1.swift: US East 1 region root certificateSources/RDSCertificates/eu-central-1.swift: EU Central 1 region root certificatePackage.swift: Swift package definition with PostgresNIO, AWSLambdaRuntime, and ServiceLifecycle dependencies
These are example applications for demonstration purposes. When deploying such infrastructure in production environments, we strongly encourage you to follow these best practices for improved security and resiliency:
- Enable access logging on API Gateway (documentation)
- Ensure that AWS Lambda function is configured for function-level concurrent execution limit (concurrency documentation, configuration guide)
- Check encryption settings for Lambda environment variables (documentation)
- Ensure that AWS Lambda function is configured for a Dead Letter Queue (DLQ) (documentation)
- Ensure that AWS Lambda function is configured inside a VPC when it needs to access private resources (documentation, code example)