Skip to content

Repository files navigation

ferristack

A single-binary local AWS emulator in Rust, drop-in compatible with ministack (and, by extension, LocalStack's core conventions). Point AWS_ENDPOINT_URL at it and your SDK clients, Terraform/OpenTofu, and Lambda functions run fully locally — with or without Docker.

Ferristack focuses on serverless use-cases (any AWS resource that scales to zero.) So it's missing some notable services. It's designed to be much faster than the alternatives, because who has time to sit around waiting for tests to pass. If you need more features of AWS consider ministack instead. If you want something that's not captured here, open an issue to start the discussion.

This project is mostly implemented by agents. It's not meant to be used in production. It's for testing only. If you find an inconsistency with AWS in production, open an issue. There are probably a ton of mistakes.

Quick start

# native (no docker)
cargo install --path .
ferristack                      # listens on 0.0.0.0:4566

# docker
docker build -t ferristack .
docker run -p 4566:4566 ferristack

Then:

export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_REGION=us-east-1
aws sqs create-queue --queue-name demo

Services

Service Fidelity
SQS Queues, send/receive/delete (+batch), visibility, long polling, delay, DLQ redrive, FIFO (dedup + group ordering), tags. Speaks both the modern JSON protocol and the legacy query/XML protocol (old SDKs, form POSTs to queue URLs).
S3 Path-style buckets/objects, multipart, ListObjectsV2, copy, tagging, Range GETs, aws-chunked decoding, bucket subresources (CORS/lifecycle/encryption/policy/...) stored + echoed. Object versioning: version stacks, delete markers, GET/DELETE ?versionId, ListObjectVersions, suspended semantics. Event notifications fire to SQS/SNS/Lambda per the bucket NotificationConfiguration (ObjectCreated/ObjectRemoved incl. DeleteMarkerCreated, prefix/suffix filters). Presigned URLs are accepted without signature or Content-Length enforcement; anonymous GETs allowed.
DynamoDB Tables + GSIs, full expression language (Update/Condition/Filter/KeyCondition/Projection), Query/Scan with pagination, batches, TransactWriteItems with CancellationReasons. TTL: expired items reaped every 60s (DYNAMODB_TTL_INTERVAL_SECONDS), lingering in reads until then as on AWS; Number epoch-seconds attribute, ignores values >5 years past, emits stream REMOVE records with the dynamodb.amazonaws.com service identity. Streams: record capture on writes (all four view types), DynamoDBStreams API (single shard, TRIM_HORIZON/LATEST/AT_/AFTER_SEQUENCE_NUMBER iterators).
Lambda provided.al2023/provided.al2 zips (inline ZipFile or S3Bucket/S3Key), versions, aliases, permissions, event source mappings for SQS and DynamoDB Streams. Function URLs: control plane + live data plane (/_aws/lambda-url/{id}/… or <id>.lambda-url.<region> hosts, payload v2). Async invokes honor EventInvokeConfig: retries, OnSuccess/OnFailure destinations to SQS/SNS/Lambda. Executes real bootstrap binaries against an embedded Lambda Runtime API — as direct subprocesses (LAMBDA_EXECUTOR=local, default) or in public.ecr.aws/lambda/provided containers (LAMBDA_EXECUTOR=docker).
EventBridge Buses, rules, full event pattern matching (prefix/suffix/anything-but/exists/numeric, nested), rate/cron scheduled rules (15s tick, AWS cron incl. ?/L/W/n#k), SQS/SNS/Lambda targets with Input/InputPath.
EventBridge Scheduler All 12 actions: schedules with rate()/cron()/one-time at() expressions, ScheduleExpressionTimezone, StartDate/EndDate windows, ClientToken idempotency, pagination; schedule groups (implicit default, deleting a group removes its schedules) with tags. Firing sweep every 10s (SCHEDULER_TICK_SECONDS): Target.Input delivered verbatim to SQS/SNS/Lambda — no EventBridge envelope, as on AWS — with <aws.scheduler.*> context attributes substituted, and ActionAfterCompletion: DELETE honored for completed one-time schedules.
API Gateway v2 HTTP APIs, routes ({param}, {proxy+}, $default), AWS_PROXY integrations (payload 2.0), JWT authorizers with live JWKS fetch (RS256), stages, ms-custom-id tag pins the API id. Invoke via /_aws/execute-api/{apiId}/{stage}{path} or {apiId}.execute-api.* hosts.
KMS Real crypto for all standard key specs (RSA 2048/3072/4096, ECC P-256/P-384/P-521/secp256k1, HMAC 224–512): Sign/Verify across the full PKCS1/PSS/ECDSA algorithm matrix, GenerateMac/VerifyMac, ECDH DeriveSharedSecret, RSA-OAEP Encrypt/Decrypt interoperable with GetPublicKey; symmetric Encrypt/Decrypt with encryption-context binding (envelope, not real encryption), data keys and data key pairs, ReEncrypt, GenerateRandom, grants, aliases, key lifecycle.
SSM Parameter Store: String/SecureString/StringList, real version history with name:version/name:label selectors and LabelParameterVersion, by-path queries, DescribeParameters filters (Name/Type/Path), DataType/AllowedPattern, tags.
Secrets Manager Secrets with versions and stage labels (AWSCURRENT/AWSPREVIOUS), ClientRequestToken idempotency, recovery-window deletes + restore, resource policies, tags, GetRandomPassword.
SES v2 SendEmail (+ v1 query API). With SMTP_HOST=host:port set, mail is relayed to that SMTP sink (e.g. Mailpit).
SNS Topics, subscriptions with real delivery to SQS (notification envelope or raw message delivery) and Lambda, filter policies (exact/prefix/exists/anything-but/numeric over attributes or body). Publish to a phone number logs SNS SMS stub to <phone>: <message> at INFO so OTP codes are greppable.
End User Messaging SMS Notify OTP messaging (pinpoint-sms-voice-v2): notify configurations (CRUD, deletion protection, ClientToken idempotency, tags), pre-seeded AWS-managed code-verification templates in 7 languages, SendNotifyTextMessage/SendNotifyVoiceMessage with template-variable resolution and DryRun, PutMessageFeedback, ListNotifyCountries, notify spend-limit overrides. Sends log Notify SMS stub to <phone>: <body> at INFO so OTP codes are greppable.
IAM, STS Roles/policies stored, never enforced; GetCallerIdentity/AssumeRole.
CloudWatch Logs Log groups/streams, retention, PutLogEvents.
CloudWatch Alarms stored + echoed; metrics accepted and discarded.
CloudFront Control plane only: distributions with ETag/If-Match semantics, OACs, instant invalidations, seeded managed cache/origin-request policies.
Route53, ACM Hosted zones/record sets (INSYNC immediately); certificates ISSUED immediately.

Everything else returns a clean 501 with the service and action named, logged at WARN.

How requests are routed

One port multiplexes all services using ministack's cascade: X-Amz-Target header prefix → SigV4 credential-scope service name → query-protocol Action parameter → path patterns (/2015-03-31/… = Lambda, /v2/apis = API Gateway, …) → Host subdomain → S3 as the fallback.

Requests are never authenticated. Any credentials work; a 12-digit AWS_ACCESS_KEY_ID becomes the account id (multi-account testing), otherwise 000000000000. Region comes from the SigV4 scope, so multi-region state isolation is automatic.

Configuration

Environment variables (ministack/LocalStack-compatible names; FERRISTACK_* variants win over MINISTACK_*):

Variable Default Purpose
GATEWAY_PORT / EDGE_PORT 4566 Listen port
BIND_HOST 0.0.0.0 Bind address
MINISTACK_HOST localhost Hostname used in generated URLs
MINISTACK_ACCOUNT_ID 000000000000 Default account id
MINISTACK_REGION us-east-1 Default region
SERVICES all Comma-separated service allowlist
DEBUG 0 Debug logging (LOG_LEVEL overrides precisely)
PERSIST_STATE 0 Persist service state across restarts
STATE_DIR /tmp/ferristack-state Where persisted state lives
S3_PERSIST 0 Persist S3 object bodies too
S3_DATA_DIR $TMPDIR/ferristack-s3 Where object bodies live
SMTP_HOST unset Relay SES mail to this host:port
LAMBDA_EXECUTOR local local (subprocess) or docker
LAMBDA_STATE_TRANSITION_SECONDS 0 Pending→Active delay for new functions
DYNAMODB_TTL_INTERVAL_SECONDS 60 Seconds between DynamoDB TTL reaper sweeps
SCHEDULER_TICK_SECONDS 10 Seconds between EventBridge Scheduler firing sweeps

Meta endpoints: GET /_localstack/health (also /_ministack/health, /_ferristack/health, /health), GET /_ministack/ready, POST /_ministack/reset.

Lambda execution without Docker

LAMBDA_EXECUTOR=local runs the zip's bootstrap directly as a subprocess and serves it the standard Lambda Runtime API, so anything cargo lambda build produces for your host platform — or any executable script named bootstrap — just runs. If you deploy Linux binaries from a macOS host, either run ferristack itself in Docker (as ministack is run) or set LAMBDA_EXECUTOR=docker, which runs each function in public.ecr.aws/lambda/provided:al2023 pointed back at ferristack's runtime API.

Terraform / OpenTofu

examples/terraform/ contains a full working stack (DynamoDB table with GSIs, S3 bucket + subresources, SQS queues with DLQ redrive, IAM, a real provided.al2023 Lambda with alias and SQS event source mapping, KMS signing key, SSM parameter, and an API Gateway HTTP API with a JWT authorizer) that applies cleanly, plans to zero diff, and destroys — the provider's read-back consistency checks all converge.

provider "aws" {
  region     = "us-east-1"
  access_key = "test"
  secret_key = "test"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true
  endpoints {
    s3       = "http://localhost:4566"
    dynamodb = "http://localhost:4566"
    lambda   = "http://localhost:4566"
    sqs      = "http://localhost:4566"
    # ... every service you use, same URL
  }
}

Development

Tool versions (Rust, OpenTofu, AWS CLI) are pinned in mise.toml; run mise install (mise) to get them.

cargo test            # integration tests drive a real server through the AWS SDKs
cargo run             # local server on :4566

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

A fast single-binary local AWS emulator in Rust for testing serverless apps — drop-in compatible with ministack/LocalStack.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages