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.
# native (no docker)
cargo install --path .
ferristack # listens on 0.0.0.0:4566
# docker
docker build -t ferristack .
docker run -p 4566:4566 ferristackThen:
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| 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.
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.
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_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.
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
}
}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 :4566Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
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.