Multi-tenancy
MiniStack is a single process, but each request is tagged with an account ID and region. State is partitioned per account — giving you LocalStack-Pro-style namespace isolation without spinning up multiple emulators.
Account inference
MiniStack picks an account ID from (in order):
- Access key ID — if the request carries a SigV4
Authorizationheader and the access key is a 12-digit string, that's the account ID. Lets you drive separate accounts with e.g.AWS_ACCESS_KEY_ID=111111111111vs222222222222. - Custom tokens with an embedded account — some tooling passes session tokens that include the account.
- Default —
000000000000(the historical LocalStack default). Some code paths still use123456789012; both normalize to the same pool under AccountScopedDict.
Regions
The region comes from the SigV4 credential scope (.../us-east-1/s3/aws4_request). Unsigned callers fall back to MINISTACK_REGION (default us-east-1).
Regional ARNs, endpoint URLs, and STS GetCallerIdentity responses all honor the per-request region. Two callers signing with different regions from the same process get region-correct responses — no single global region setting is used at request time.
Since 1.4.0, state is additionally isolated per region for AppConfig, Bedrock (all four services), CloudWatch, CloudWatch Logs, DynamoDB, Lambda, MSK, RDS, S3 Tables, Secrets Manager, SQS, SSM Parameter Store, and Step Functions — and, since 1.4.1, EventBridge Pipes, and since 1.4.2, SNS, Kinesis, KMS, and ElastiCache, since 1.4.3, EventBridge, ECS, and Firehose, since 1.4.4, EventBridge Scheduler, CodeBuild, and Resource Groups, and since 1.4.5, Batch and SES, and since 1.4.6, Athena, Auto Scaling, Cloud Map, EFS, EMR, Inspector2, Amazon MQ, AppSync, and S3 Files — two clients signing with different regions see fully independent resources, and cross-region ARN references return the same errors real AWS returns. The remaining services — S3, IAM/STS, EC2, ECR, EKS, Glue, API Gateway v1/v2, Cognito, CloudFormation, CloudFront, Route 53, WAF, ACM, Backup, Organizations, Transfer Family, CloudTrail, and the remaining control-plane services — still share state across regions within an account; if you exercise two regions against those, use unique resource names to avoid collisions. Region isolation for them lands in subsequent releases.
AccountScopedDict
AccountScopedDict (in ministack/core/) is a dict wrapper that transparently partitions keys by the current request's account ID. A service module writes self._tables[table_name] = … as if it's a flat dict; the wrapper stores it under (account_id, table_name). When account 111…111 reads _tables, it sees only its own entries.
# Inside a service module self._queues = AccountScopedDict() self._queues[name] = queue_data # scoped to request.account_id automatically
The request's account is set via a context-local (set_request_account_id()) at the top of the router, so handlers can stay oblivious.
Leaky services
Not every service is wrapped. A handful still use plain dicts — meaning two accounts can see each other's data. These are the known leaks as of 1.3.50:
| Service | Nature of the leak |
|---|---|
| CloudWatch metrics | Plain-dict metric store; alarms and metric data bleed across accounts. |
| EventBridge (parts) | Some internal maps (event bus, rule state) are not fully scoped. |
| SES | Sent-message store is per-account-filterable at /_ministack/ses/messages, but underlying maps are shared. |
| ElastiCache | Container tracking is by logical cluster name — two accounts creating the same name collide. |
See Known limitations for the full cross-service gap list.
Using multiple accounts
Set distinct access keys for each logical account. No other configuration is needed.
export AWS_ACCESS_KEY_ID=111111111111 aws --endpoint-url=http://localhost:4566 s3 mb s3://account-one-bucket export AWS_ACCESS_KEY_ID=222222222222 aws --endpoint-url=http://localhost:4566 s3 mb s3://account-two-bucket aws --endpoint-url=http://localhost:4566 s3 ls # only sees account-two-bucket
In Boto3/SDKs, construct one client per account with its own credential pair. The secret key is ignored but must be set — test is fine.