Known limitations
MiniStack aims for AWS compatibility, but some integrations and infrastructure concepts don't translate to a single local process. This page is the honest, cross-service gap list.
If a gap blocks you, open an issue. We actively closes parity gaps — several items on this list are first-class work items, not permanent compromises. Dated against MiniStack 1.5.32.
Stored but not dispatched
These integrations accept configuration and return correct shapes, but the side-effect is not performed. Tests that assert on the stored config pass; tests that assert on downstream side-effects will not.
| Surface | What's missing |
|---|---|
| CloudWatch Alarm → Lambda / other targets | SNS alarm actions fire on state transition; Lambda and other action targets are stored but not invoked. |
| CloudWatch Metrics for Lambda | Invocations, Errors, Duration and Throttles are emitted; ConcurrentExecutions is not. |
| CloudWatch Metrics for SQS | ApproximateNumberOfMessagesVisible, ApproximateAgeOfOldestMessage not tracked. |
| EventBridge → API Destination retries | API destinations make real outbound HTTP calls (with OAuth refresh); AWS's 24-hour/185-attempt retry pipeline and DLQ are not modeled — a failed delivery is logged and dropped. |
| EventBridge Pipes | DynamoDB Streams → SNS and Step Functions pipes deliver (background poller); other source/target combinations are stored but not piped. |
| ECS → CloudWatch Logs (awslogs driver) | Log driver config parsed; stdout/stderr not written to log groups. |
| API Gateway access logs | AccessLogSettings stored; no log events written. |
| Step Functions logging | loggingConfiguration stored; not written to CloudWatch Logs. No ExecutionsStarted/Failed/Duration metrics. |
| CodeBuild logs | Metadata-only builds (the default) write nothing; with MINISTACK_CODEBUILD_EXECUTE=1 build output does stream to CloudWatch Logs. |
| WAFv2 rule evaluation | WebACLs, rules, IP sets all stored. Rules are not enforced against incoming requests. |
| AutoScaling policy triggers | Scaling policies + lifecycle hooks stored; never fired by CloudWatch alarms. |
| CloudFormation Stack Policy | SetStackPolicy / GetStackPolicy store and return the policy; updates are never policy-gated. |
| Cognito Lambda triggers | PreTokenGeneration, the federated PreSignUp, the CUSTOM_AUTH triggers and CustomMessage_AdminCreateUser are invoked; PostConfirmation, Pre/PostAuthentication and the other CustomMessage sources are stored but not invoked. |
| SES identity verification | VerifyEmailIdentity / VerifyDomainIdentity jump straight to Success — no pending state, no confirmation email. |
| Route53 health checks | Checks stored; status never updated; no CloudWatch bridge. |
| S3 SSE-KMS | SSE headers validated, stored and echoed (SSE-C key checks enforced); no real cryptography — object bytes stored as sent. |
CloudFormation AWS::DynamoDB::GlobalTable Replicas | Accepted and ignored by the stack. Replicas do work through UpdateTable ReplicaUpdates (Terraform / OpenTofu replica blocks) since 1.5.20, alongside S3 replication (1.5.3) and KMS multi-Region keys (1.5.5). |
| ECS task state → EventBridge | SubmitTaskStateChange exists; event is not put on the default bus. |
Metadata-only services
These services accept and return realistic shapes so IaC tools plan and apply, but no real infrastructure is created:
- EC2 — instances, VPCs, subnets, and security groups exist as data by default (no ENI networking). Since 1.5.0,
RegisterImagecan back an instance with a real container so it is reachable andssm:SendCommandruns on it (opt-in). - CloudFront — a distribution serves viewer requests from its S3 or custom origins at
<label>.cloudfront.net; no caching, WAF, logging, geo restrictions, custom error pages, signed URLs or cookies, or Lambda@Edge. - Transfer Family — a real SFTP listener is bound (default port 2222, requires asyncssh); FTPS/FTP are not implemented.
- EFS — file systems, mount targets, access points stored; no POSIX filesystem or NFS mount.
- AppSync — GraphQL queries and mutations execute against DynamoDB and Lambda data sources (best-effort parser; VTL mapping templates are stored, not interpreted).
- EMR — cluster and step metadata tracked; no Spark/Hadoop execution. Glue is different: python-shell jobs run as a subprocess and Spark (glueetl) jobs run in Docker when available.
- ACM — certificates auto-ISSUED; no DNS/HTTP validation occurs.
- Athena without DuckDB — with
ATHENA_ENGINE=mock, results are synthetic (amock_valuerow, or echoed SELECT literals).auto/duckdbgives real SQL on S3 data. - Firehose non-S3 destinations — HTTP, Redshift, OpenSearch, Splunk, Snowflake all stored; no delivery performed. (S3/ExtendedS3 and Iceberg do deliver.)
Impossible locally
These are structural — a single-process emulator cannot simulate them.
- Real VPC networking. Subnets, route tables, and NAT all exist as metadata; packet routing is not simulated.
- Real DNS propagation. Route53 changes are visible inside MiniStack only; they don't affect your host's resolver.
- SMTP delivery without a real MTA. Unless
SMTP_HOSTpoints at one (e.g. MailHog), SES emails stay in memory. - Real Kubernetes control plane. EKS runs k3s as a sidecar — powerful, but not byte-identical to EKS.
By-design differences
- SigV4 signatures are checked only under
AUTH=true, and only for IAM user and STS session keys. WithAUTHoff, or with a root key (test, the server'sAWS_ACCESS_KEY_IDor a 12-digit account ID), any access key/secret works. - Default account
000000000000(some paths use123456789012interchangeably). - State shared across regions — only for Aurora DSQL; every other regional service is region-isolated as of 1.5.4 (use unique names for DSQL when exercising two regions). S3 bucket names are global as on AWS, and since 1.5.28 each bucket keeps its home region and answers a request signed for another region with
301 PermanentRedirect. IAM, STS, CloudFront, Route 53, and Organizations are global services in AWS, so account-scoped shared state is correct for them, not a gap. - Lenient validation. Required fields are checked; optional fields are accepted more permissively than AWS.
- Cognito tokens carry the AWS issuer. A pool's tokens use
https://cognito-idp.{region}.amazonaws.com/{poolId}asiss, exactly as on AWS. To let an unmodified client (Spring, Vault,aws-jwt-verify) follow it to MiniStack, run withUSE_SSL=1and point the issuer host at MiniStack:docker run -d --name ministack -p 443:4566 -p 4566:4566 -e USE_SSL=1 ministackorg/ministack docker cp ministack:/tmp/ministack-tls/server.crt ./ministack-ca.pem # /etc/hosts 127.0.0.1 cognito-idp.us-east-1.amazonaws.com
Then trustministack-ca.pemin your client's runtime; the OS keychain is not enough for these: Javakeytool -importcert -file ministack-ca.pem -alias ministack -cacerts -storepass changeit, NodeNODE_EXTRA_CA_CERTS=./ministack-ca.pem, PythonREQUESTS_CA_BUNDLE/AWS_CA_BUNDLEpointing at a bundle of your system roots plusministack-ca.pem. The certificate covers every region's issuer host, and Lambda containers MiniStack starts get the hosts entries and trust automatically. Remove the/etc/hostsline before calling the real Cognito.
Per-service detail: each entry on the Services index page links to a service page with its own "Known limitations" section, calling out the gaps that matter for that specific API surface.