why
In March 2026 LocalStack archived its open-source repo and folded everything into an account-based model. The free Hobby tier that replaced the old Community Edition is explicitly non-commercial only, and the archived OSS image no longer gets security patches. That left a real gap: there was no genuinely free local AWS emulator you could ship inside a commercial CI pipeline.
MockCloud is that emulator. MIT licensed, no account, no token, no EULA, and a real console UI baked in.
how it works
A Node.js core listens on :4566 for AWS API calls. A request dispatcher routes each call to one of seventeen isolated service handlers in src/services/:
- Storage: S3, DynamoDB, DynamoDB Streams
- Compute: Lambda, EC2, Step Functions
- Messaging: SNS, SQS, EventBridge
- Security: IAM / STS, Secrets Manager, KMS, Cognito
- Ops: CloudWatch, SSM Parameter Store, SES, API Gateway
State lives in an in-memory store, with CloudWatch metrics in a ring buffer. S3 is the only service that persists to disk (~/.mockcloud/s3/); everything else is ephemeral and resets on restart.
A React + Vite dashboard runs on :4567 and reads state via /mockcloud/* endpoints on the same Node process. It’s the differentiator: no other free AWS emulator ships a UI in the box.
cross-service event wiring
Cross-service triggers actually fire instead of being stubbed. The in-memory event bus runs three subscriptions out of the box:
SNS→LambdaDynamoDB Streams→LambdaEventBridge→SQS
This was the design decision that took the longest to get right, and the one I’m proudest of.
EC2 dual mode
EC2 has two modes. Simulated (default) is fast and needs nothing on the host. Docker-backed spawns real containers and tracks them by label for state reconciliation. The emulator auto-detects whether the Docker daemon is up and picks the mode automatically; contributors don’t need Docker installed just to run the test suite.
connecting
Point any AWS SDK at the local endpoint with throwaway credentials:
const s3 = new S3Client({
endpoint: "http://127.0.0.1:4566",
credentials: { accessKeyId: "local", secretAccessKey: "local" },
});