A high-fidelity AWS local emulator written in Rust. Drop-in replacement for LocalStack for integration testing.
- Fast - Starts in milliseconds, not seconds
- Light - ~50MB memory vs 300MB+ for LocalStack
- Simple - Single binary, no Docker/Java required
- Compatible - Same port (4566), same API, same error codes
| Service | Operations | Notes |
|---|---|---|
| S3 | Buckets, objects, multipart upload, copy | XML responses, proper ETags |
| DynamoDB | Tables, items, query, scan, batch ops | Full expression support |
| Lambda | CRUD, invoke, environment vars, layers | Python/Docker execution (with Node.js) |
| CloudWatch Logs | Groups, streams, events | For Lambda log retrieval |
| Secrets Manager | Create, get, put, delete, list | Version stages, SQLite persistence |
| IAM | Roles, policies, attachments | Policy evaluation engine (ENFORCE_IAM=1) |
| API Gateway V2 | APIs, routes, integrations, stages | HTTP APIs, Lambda integrations |
| Kinesis Firehose | Delivery streams, put records | In-memory buffering |
| SQS | Queues, send, receive, delete messages | In-memory queue storage |
| SNS | Topics, subscriptions, publish | Pub/sub messaging with SQS fan-out |
| Cognito | User pools, Auth, Admin API | Local JWT generation |
| Step Functions | State machines, executions | Offline ASL execution |
| CloudFormation | Stack deployment, CDK support | Local resource instantiation |
# Build
cargo build --release
# Run (default port 4566)
./target/release/ruststack
# With debug logging
RUST_LOG=debug ./target/release/ruststackimport boto3
endpoint_url = "http://localhost:4566"
# S3
s3 = boto3.client("s3", endpoint_url=endpoint_url,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
# DynamoDB
dynamodb = boto3.client("dynamodb", endpoint_url=endpoint_url,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
# Lambda
lambda_client = boto3.client("lambda", endpoint_url=endpoint_url,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
# SQS
sqs = boto3.client("sqs", endpoint_url=endpoint_url,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
# SNS
sns = boto3.client("sns", endpoint_url=endpoint_url,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
## Testing
Use the `ruststack-test` crate for integration tests:
```rust
use ruststack_test::{TestServer, RustStackClient};
#[tokio::test]
async fn test_sqs() {
let server = TestServer::start().await.unwrap();
let client = server.client();
// Create queue and send message
let queue_url = client.create_queue("my-queue").await.unwrap();
client.send_message(&queue_url, "Hello!").await.unwrap();
// Reset state between tests
server.reset().await;
}You can use RustStack directly in your Python test suites without running any Docker containers or external processes for minimal-overhead local testing.
import ruststack_py
import boto3
# Initialize in-process RustStack
rs = ruststack_py.RustStack()
# Use boto3 as usual, pointing to the local endpoint
s3 = boto3.client("s3", endpoint_url="http://localhost:4566",
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")Replace LocalStack with RustStack in your tests:
import subprocess
import time
import pytest
import requests
@pytest.fixture(scope="session")
def ruststack():
"""Start RustStack for the test session."""
proc = subprocess.Popen(
["./target/release/ruststack", "--port", "4566"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
# Wait for ready
for _ in range(30):
try:
if requests.get("http://localhost:4566/health").status_code == 200:
break
except requests.ConnectionError:
pass
time.sleep(0.1)
else:
proc.kill()
raise RuntimeError("RustStack failed to start")
yield "http://localhost:4566"
proc.terminate()
proc.wait()
@pytest.fixture
def s3_client(ruststack):
import boto3
return boto3.client("s3", endpoint_url=ruststack,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
@pytest.fixture
def dynamodb_client(ruststack):
import boto3
return boto3.client("dynamodb", endpoint_url=ruststack,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
@pytest.fixture
def lambda_client(ruststack):
import boto3
return boto3.client("lambda", endpoint_url=ruststack,
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")import json
event = {
"version": "2.0",
"routeKey": "GET /patient-check/{id}",
"rawPath": "/patient-check/1234567890",
"rawQueryString": "",
"headers": {
"content-type": "application/json",
"nhs-login-nhs-number": "1234567890",
},
"pathParameters": {"id": "1234567890"},
"requestContext": {
"http": {"method": "GET", "path": "/patient-check/1234567890"},
"requestId": "test-request-id",
},
"body": None,
"isBase64Encoded": False,
}
response = lambda_client.invoke(
FunctionName="my-function",
InvocationType="RequestResponse",
Payload=json.dumps(event),
LogType="Tail", # Get logs in response
)Usage: ruststack [OPTIONS]
Options:
-p, --port <PORT> Port to listen on [default: 4566]
--host <HOST> Host to bind to [default: 0.0.0.0]
--lambda-executor <MODE> Lambda executor: subprocess, docker, auto [default: subprocess]
--lambda-container-ttl <SECS> Docker container TTL for warm pool [default: 300]
--lambda-max-containers <N> Maximum concurrent Lambda containers [default: 10]
--lambda-network <MODE> Docker network mode: bridge or host [default: bridge]
-h, --help Print help
RustStack supports two Lambda execution modes:
| Mode | Cold Start | Isolation | Dependencies |
|---|---|---|---|
| subprocess (default) | ~10-50ms | None | Must be installed on host |
| docker | ~500ms-2s | Full container | Bundled in container |
# Fast development mode (subprocess)
ruststack
# Isolated mode (Docker containers)
ruststack --lambda-executor docker
# Auto mode (Docker for non-Python runtimes)
ruststack --lambda-executor autoLambda layers are supported in Docker mode. Layers are ZIP files that get extracted to /opt/ inside the container:
import boto3
lambda_client = boto3.client("lambda", endpoint_url="http://localhost:4566",
aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")
lambda_client.create_function(
FunctionName='my-function',
Runtime='python3.12',
Handler='handler.main',
Role='arn:aws:iam::123456789012:role/lambda-role',
Code={
'ZipFile': open('function.zip', 'rb').read(),
# Local paths to layer ZIP files
'Layers': ['/path/to/numpy-layer.zip', '/path/to/shared-libs.zip']
}
)Layers are extracted to /opt/ at container startup. Python automatically adds /opt/python/lib/python3.X/site-packages/ to PYTHONPATH.
You can deploy Lambda functions with code stored in S3:
# First upload code to S3
s3 = boto3.client("s3", endpoint_url="http://localhost:4566", ...)
s3.put_object(Bucket='my-bucket', Key='function.zip', Body=open('function.zip', 'rb').read())
# Create function with S3 code
lambda_client.create_function(
FunctionName='my-function',
Runtime='python3.12',
Handler='handler.main',
Role='arn:aws:iam::123456789012:role/lambda-role',
Code={
'S3Bucket': 'my-bucket',
'S3Key': 'function.zip'
}
)This is useful for large functions or when you want to share code between environments.
See docs/DOCKER_LAMBDA.md for detailed Docker configuration.
curl http://localhost:4566/health
# or LocalStack-compatible:
curl http://localhost:4566/_localstack/health- CreateBucket, DeleteBucket, ListBuckets, HeadBucket
- PutObject, GetObject, DeleteObject, HeadObject, CopyObject
- ListObjects, ListObjectsV2
- CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload
- ListMultipartUploads, ListParts
- CreateTable, DeleteTable, DescribeTable, ListTables
- PutItem, GetItem, DeleteItem, UpdateItem
- Query, Scan, BatchGetItem, BatchWriteItem
- Full expression support: KeyConditionExpression, FilterExpression, UpdateExpression, ConditionExpression, ProjectionExpression
- GSI and LSI support
- CreateFunction, GetFunction, DeleteFunction, ListFunctions
- Invoke (RequestResponse, Event)
- UpdateFunctionCode, UpdateFunctionConfiguration
- Environment variables, Python runtime
- Lambda Layers (local zip files)
- Code deployment from S3
- CreateLogGroup, CreateLogStream, DeleteLogGroup
- DescribeLogGroups, DescribeLogStreams
- PutLogEvents, GetLogEvents
- CreateSecret, GetSecretValue, PutSecretValue
- DeleteSecret, DescribeSecret, ListSecrets
- Version stages: AWSCURRENT, AWSPREVIOUS
- CreateRole, GetRole, DeleteRole, ListRoles
- CreatePolicy, GetPolicy, DeletePolicy
- AttachRolePolicy, DetachRolePolicy, ListAttachedRolePolicies
- Deterministic policy evaluation engine (with
ENFORCE_IAM=1flag)
- CreateApi, GetApi, DeleteApi, GetApis
- CreateRoute, GetRoute, DeleteRoute, GetRoutes
- CreateIntegration, GetIntegration, DeleteIntegration, GetIntegrations
- CreateStage, GetStage, DeleteStage, GetStages
- CreateDeliveryStream, DeleteDeliveryStream
- DescribeDeliveryStream, ListDeliveryStreams
- PutRecord, PutRecordBatch
- Note: Records are buffered in memory
- CreateUserPool, ListUserPools
- AdminCreateUser, AdminGetUser, AdminEnableUser, AdminDisableUser, AdminDeleteUser
- InitiateAuth (JWT token generation)
- CreateStateMachine, DescribeStateMachine, ListStateMachines, DeleteStateMachine
- StartExecution, DescribeExecution, ListExecutions, StopExecution
- ASL states: Pass, Task, Choice, Wait, Succeed, Fail, Parallel, Map
- DescribeStacks, DeleteStack
- Template parsing (YAML/JSON), CDK dependency resolution, deterministic resource instantiation
FROM rust:1.75-slim AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
COPY --from=builder /app/target/release/ruststack /usr/local/bin/
EXPOSE 4566
ENTRYPOINT ["ruststack"]docker build -t ruststack .
docker run -p 4566:4566 ruststack| Variable | Description | Default |
|---|---|---|
RUST_LOG |
Log level/filter | info |
RUSTSTACK_LAMBDA_EXECUTOR |
Lambda executor mode | subprocess |
RUSTSTACK_LAMBDA_CONTAINER_TTL |
Docker warm container TTL (seconds) | 300 |
RUSTSTACK_LAMBDA_MAX_CONTAINERS |
Max concurrent Lambda containers | 10 |
RUSTSTACK_LAMBDA_NETWORK |
Docker network mode | bridge |
| RustStack | LocalStack | |
|---|---|---|
| Startup | ~10ms | ~5-10s |
| Memory | ~50MB | ~300MB+ |
| Dependencies | None (Docker optional) | Docker, Java |
| Lambda execution | Subprocess or Docker | Container |
| Persistence | In-memory | Optional |
| Services | S3, DynamoDB, Lambda, Logs | 80+ |
- ~28,200 lines of Rust
- 290+ tests with comprehensive coverage
- 17 crates
- CI/CD via GitHub Actions
Tagged releases automatically build binaries for:
- Linux x86_64
- macOS x86_64
- macOS arm64 (Apple Silicon)
# Create a release
git tag v0.1.0
git push --tagsSee ARCHITECTURE.md for design details and PLAN.md for the roadmap.
MIT OR Apache-2.0