May 23, 2026 · v1.3.47
One new CloudFormation feature, three contributor bug fixes, two Cognito quality-of-life improvements for federated sign-in debugging.
AWS::CloudFormation::Stack)Templates that compose other templates now work. AWS::CloudFormation::Stack resources fetch their child template from TemplateURL (resolved against the local S3 emulator), pass Parameters through to the child, provision the child's resources inline, and expose the child's Outputs back to the parent via the standard Fn::GetAtt: [NestedRes, Outputs.<Name>] sub-attribute form — the same syntax CDK emits when you use the NestedStack construct.
Resources:
Storage:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: http://localhost:4566/templates/storage.yaml
Parameters:
BucketSuffix: prod
AppParam:
Type: AWS::SSM::Parameter
Properties:
Name: /app/storage-bucket
Type: String
Value: !GetAtt Storage.Outputs.BucketName
Ref of the nested resource resolves to the child stack's StackId ARN, matching real AWS — so DescribeStacks on the Ref works. Delete and update propagate to the child. The child stack gets its own _stacks entry with RootId / ParentId pointers, so it shows up in the events feed under its own ID just like AWS. Reported by @jayalfredprufrock.
CreateInvalidation caller-reference idempotencyPer the CloudFront API, re-submitting CreateInvalidation with a previously-used CallerReference on the same distribution should return the existing invalidation rather than creating a duplicate. MiniStack now does that. Path comparison is set-based — re-submitting the same paths in a different order counts as the same batch (real AWS treats the paths as a set), so well-behaved retry loops don't trip the divergent-batch path. Contributed by @CoffeeRaptor.
DeleteObjects removes keys from diskSingular DeleteObject already cleaned up the persisted file plus its .meta.json sidecar, but the batch DeleteObjects path only purged in-memory metadata, leaving orphaned files on disk after a S3_PERSIST=1 bucket cleanup. DeleteObjects now calls the same _delete_persisted_object helper per deleted key, mirroring DeleteObject. Contributed by @parafoxia.
With PERSIST_STATE=1, a restart used to leave persisted DB instances marked available with no backing container — zombie metadata that StartDBInstance couldn't recover from because it's metadata-only. The restore path now spawns a daemon thread per persisted instance, removes any stale container under the deterministic ministack-rds-<db_id> name, runs a fresh container with the persisted volume + saved credentials, and transitions the instance creating → available on container start (or failed on Docker error). Each daemon thread inherits the original account's _request_account_id contextvar so multi-tenant restores land writes on the right account.
/oauth2/idpresponse and /saml2/idpresponse diagnosticsBoth federated-callback endpoints used to return an opaque InvalidParameterException for three different failure modes (missing code, missing state, or no matching pending-auth entry). Each branch now carries a distinct message naming the exact field plus a server-side WARNING log when the relay key from the IdP doesn't match any pending authorize flow:
Cognito OIDC callback: no matching relay for state=<hash> (expired after 300s, already consumed, or never issued by /oauth2/authorize). Re-start the federated login flow from /oauth2/authorize.
So configuration drift in your IdP (Keycloak, Auth0, Okta) becomes diagnosable from the response body alone, without having to grep the server log for what the 400 actually meant. Reported by @ocr-lasagna.
.well-known/* no longer shadows S3GET /<poolId>/.well-known/jwks.json and GET /<poolId>/.well-known/openid-configuration now check whether poolId is a registered user pool before serving a Cognito response. If it isn't, the request falls through to the S3 handler — so apps that store their own OIDC discovery doc as an S3 object under a .well-known/ key prefix get the actual object back instead of a fake Cognito JWKS body. Real AWS only serves these for actual pools.
docker pull ministackorg/ministack:1.3.47 docker run -d -p 4566:4566 ministackorg/ministack:1.3.47
Or pin in compose.yaml:
services:
ministack:
image: ministackorg/ministack:1.3.47
ports:
- "4566:4566"
Issues and PRs welcome on GitHub. Discussion on r/ministack.