Skip to content

eddalmond/ruststack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

118 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RustStack

CI License

A high-fidelity AWS local emulator written in Rust. Drop-in replacement for LocalStack for integration testing.

Why RustStack?

  • 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

Features

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

Quick Start

# Build
cargo build --release

# Run (default port 4566)
./target/release/ruststack

# With debug logging
RUST_LOG=debug ./target/release/ruststack

Usage with boto3

import 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;
}

Python Native Bindings (ruststack-py)

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")

pytest Fixture

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")

Lambda Invocation (API Gateway v2 Format)

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
)

CLI Options

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

Lambda Execution Modes

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 auto

Lambda Layers

Lambda 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.

Lambda Deployment from S3

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.

Health Check

curl http://localhost:4566/health
# or LocalStack-compatible:
curl http://localhost:4566/_localstack/health

Supported Operations

S3

  • CreateBucket, DeleteBucket, ListBuckets, HeadBucket
  • PutObject, GetObject, DeleteObject, HeadObject, CopyObject
  • ListObjects, ListObjectsV2
  • CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload
  • ListMultipartUploads, ListParts

DynamoDB

  • CreateTable, DeleteTable, DescribeTable, ListTables
  • PutItem, GetItem, DeleteItem, UpdateItem
  • Query, Scan, BatchGetItem, BatchWriteItem
  • Full expression support: KeyConditionExpression, FilterExpression, UpdateExpression, ConditionExpression, ProjectionExpression
  • GSI and LSI support

Lambda

  • CreateFunction, GetFunction, DeleteFunction, ListFunctions
  • Invoke (RequestResponse, Event)
  • UpdateFunctionCode, UpdateFunctionConfiguration
  • Environment variables, Python runtime
  • Lambda Layers (local zip files)
  • Code deployment from S3

CloudWatch Logs

  • CreateLogGroup, CreateLogStream, DeleteLogGroup
  • DescribeLogGroups, DescribeLogStreams
  • PutLogEvents, GetLogEvents

Secrets Manager

  • CreateSecret, GetSecretValue, PutSecretValue
  • DeleteSecret, DescribeSecret, ListSecrets
  • Version stages: AWSCURRENT, AWSPREVIOUS

IAM

  • CreateRole, GetRole, DeleteRole, ListRoles
  • CreatePolicy, GetPolicy, DeletePolicy
  • AttachRolePolicy, DetachRolePolicy, ListAttachedRolePolicies
  • Deterministic policy evaluation engine (with ENFORCE_IAM=1 flag)

API Gateway V2 (HTTP APIs)

  • CreateApi, GetApi, DeleteApi, GetApis
  • CreateRoute, GetRoute, DeleteRoute, GetRoutes
  • CreateIntegration, GetIntegration, DeleteIntegration, GetIntegrations
  • CreateStage, GetStage, DeleteStage, GetStages

Kinesis Firehose

  • CreateDeliveryStream, DeleteDeliveryStream
  • DescribeDeliveryStream, ListDeliveryStreams
  • PutRecord, PutRecordBatch
  • Note: Records are buffered in memory

Cognito

  • CreateUserPool, ListUserPools
  • AdminCreateUser, AdminGetUser, AdminEnableUser, AdminDisableUser, AdminDeleteUser
  • InitiateAuth (JWT token generation)

Step Functions

  • CreateStateMachine, DescribeStateMachine, ListStateMachines, DeleteStateMachine
  • StartExecution, DescribeExecution, ListExecutions, StopExecution
  • ASL states: Pass, Task, Choice, Wait, Succeed, Fail, Parallel, Map

CloudFormation

  • DescribeStacks, DeleteStack
  • Template parsing (YAML/JSON), CDK dependency resolution, deterministic resource instantiation

Docker

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

Environment Variables

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

Differences from LocalStack

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+

Project Stats

  • ~28,200 lines of Rust
  • 290+ tests with comprehensive coverage
  • 17 crates
  • CI/CD via GitHub Actions

Releases

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 --tags

Contributing

See ARCHITECTURE.md for design details and PLAN.md for the roadmap.

License

MIT OR Apache-2.0

About

High-fidelity AWS local emulator in Rust (S3, DynamoDB, Lambda)

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages