DocsAWS 101BlogServices

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.

SurfaceWhat's missing
CloudWatch Alarm → Lambda / other targetsSNS alarm actions fire on state transition; Lambda and other action targets are stored but not invoked.
CloudWatch Metrics for LambdaInvocations, Errors, Duration and Throttles are emitted; ConcurrentExecutions is not.
CloudWatch Metrics for SQSApproximateNumberOfMessagesVisible, ApproximateAgeOfOldestMessage not tracked.
EventBridge → API Destination retriesAPI 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 PipesDynamoDB 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 logsAccessLogSettings stored; no log events written.
Step Functions loggingloggingConfiguration stored; not written to CloudWatch Logs. No ExecutionsStarted/Failed/Duration metrics.
CodeBuild logsMetadata-only builds (the default) write nothing; with MINISTACK_CODEBUILD_EXECUTE=1 build output does stream to CloudWatch Logs.
WAFv2 rule evaluationWebACLs, rules, IP sets all stored. Rules are not enforced against incoming requests.
AutoScaling policy triggersScaling policies + lifecycle hooks stored; never fired by CloudWatch alarms.
CloudFormation Stack PolicySetStackPolicy / GetStackPolicy store and return the policy; updates are never policy-gated.
Cognito Lambda triggersPreTokenGeneration, 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 verificationVerifyEmailIdentity / VerifyDomainIdentity jump straight to Success — no pending state, no confirmation email.
Route53 health checksChecks stored; status never updated; no CloudWatch bridge.
S3 SSE-KMSSSE headers validated, stored and echoed (SSE-C key checks enforced); no real cryptography — object bytes stored as sent.
CloudFormation AWS::DynamoDB::GlobalTable ReplicasAccepted 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 → EventBridgeSubmitTaskStateChange 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, RegisterImage can back an instance with a real container so it is reachable and ssm:SendCommand runs 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 (a mock_value row, or echoed SELECT literals). auto/duckdb gives 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_HOST points 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. With AUTH off, or with a root key (test, the server's AWS_ACCESS_KEY_ID or a 12-digit account ID), any access key/secret works.
  • Default account 000000000000 (some paths use 123456789012 interchangeably).
  • 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} as iss, exactly as on AWS. To let an unmodified client (Spring, Vault, aws-jwt-verify) follow it to MiniStack, run with USE_SSL=1 and 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 trust ministack-ca.pem in your client's runtime; the OS keychain is not enough for these: Java keytool -importcert -file ministack-ca.pem -alias ministack -cacerts -storepass changeit, Node NODE_EXTRA_CA_CERTS=./ministack-ca.pem, Python REQUESTS_CA_BUNDLE / AWS_CA_BUNDLE pointing at a bundle of your system roots plus ministack-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/hosts line 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.