April 29, 2026 · v1.3.21
1.3.21 turns ElastiCache replication groups into real Redis containers, adds opt-in real Redis Cluster mode for cluster-aware clients, fixes a 16-site XML wrapper bug that made aws-sdk-go-v2 and Java SDK v2 see empty lists where boto3 saw populated ones, drops the stale Fault suffix from two RDS not-found error codes that were breaking the ACK RDS controller, and adds opaque base64url pagination to ten REST API operations. Most of these are the kind of bug that only one SDK family notices, which is exactly the slice of AWS parity ministack tries to close.
CreateReplicationGroup previously returned a metadata-only stub. It now spawns live Redis containers per shard so SDK clients can connect to a routable endpoint and run real commands. Behind ELASTICACHE_CLUSTER_MODE_REAL=1 together with a DOCKER_NETWORK, NumNodeGroups=N with ReplicasPerNodeGroup=R provisions N × (1 + R) cluster-enabled nodes, runs redis-cli --cluster create with the right master / replica wiring, and serves real CLUSTER SLOTS / MOVED redirects, so a Lettuce or redis-py-cluster client behaves the way it would against a real ElastiCache cluster.
AWS rejects NumNodeGroups=2 (cluster mode requires either 1 shard or 3 or more); ministack now rejects it too with InvalidParameterValue. Container names are account-scoped and labeled with account_id so two accounts can share the same ReplicationGroupId, and an orphan-container reaper runs at startup so a hard restart does not leak stale containers. Reported by @akursar.
<member> swapped for AWS-spec element namesDescribeCacheClusters, DescribeReplicationGroups, DescribeCacheSubnetGroups, and 13 other list-emitting ops were wrapping items in <member>. The Query-protocol contract is more specific: each list shape declares a locationName in the model (<CacheCluster>, <ReplicationGroup>, <Subnet>, <CacheSubnetGroup>, <Snapshot>, <Tag>, ...). Strict generated SDKs (aws-sdk-go-v2, aws-sdk-java-v2, aws-sdk-rust) drive their decoder off that name, so a <member>-wrapped list comes back empty. botocore is permissive (it accepts both forms), which is why boto3 / AWS CLI users never saw it.
Sixteen sites now emit the correct element name. The five remaining <member> sites (UserList, UserGroupList, UserIdList, UserGroupIdList, UGReplicationGroupIdList) are intentional — those shapes really do use <member> in service-2.json. Verified one-to-one against botocore/data/elasticache/2015-02-02/service-2.json. Reported by @jmickey in #530.
DBInstanceNotFound, DBParameterGroupNotFoundTwo RDS error codes carried a stale Fault suffix on the wire: DescribeDBInstances and seven other DBInstance ops emitted <Code>DBInstanceNotFoundFault</Code> while real AWS returns DBInstanceNotFound; DescribeDBParameters and nine other DBParameterGroup ops emitted DBParameterGroupNotFoundFault while real AWS returns DBParameterGroupNotFound. RDS has a documented gap between shape names and wire error.code on roughly 19 not-found errors; these were the two that ministack was emitting with the wrong wire code.
This breaks string-matching consumers like the ACK RDS controller's sdkFind, which compares awsErr.ErrorCode() == "DBInstanceNotFound" to detect the not-found branch and reach the create path. With the Fault suffix the branch never matched and the custom resource sat at Ready=False forever. Same hit on aws-sdk-go-v2's smithy.APIError.ErrorCode() and any boto3 caller matching on e.response["Error"]["Code"]. Verified against botocore/data/rds/2014-10-31/service-2.json. Reported by @jmickey.
While there: RDS error envelopes now also include <Type>Sender</Type> for 4xx and <Type>Receiver</Type> for 5xx, completing the documented Query-protocol error shape.
GetRestApis, GetResources, GetDeployments, GetAuthorizers, GetModels, GetApiKeys, GetUsagePlans, GetUsagePlanKeys, GetDomainNames, and GetBasePathMappings previously ignored the AWS-spec limit (default 25, max 500) and position query parameters and always returned the full list with no position cursor. Pagination-aware SDKs (boto3 paginators, AWS CLI --max-items / --starting-token, Java SDK v2) silently received the same first page on every call. The ten ops now slice per limit, return an opaque base64url-encoded position token when more pages remain, and reject malformed tokens with BadRequestException. GetStages is correctly not paginated — its AWS shape has no limit / position fields.
PutMethodResponse and PutIntegrationResponse now return HTTP 201 on creation instead of 200. Real AWS returns 201 for both (verified against botocore/data/apigateway/2015-07-09/service-2.json); the AWS CLI prints the resource on 201 and is silent on 200, so scripts that branched on stderr would diverge. The remaining v1 Create / Put ops were already correctly returning 201.
# Regular image (Alpine) docker pull ministackorg/ministack:1.3.21 # Full image (Debian + DuckDB + psycopg2 + pymysql) docker pull ministackorg/ministack:1.3.21-full # pip pip install -U ministack
Full changelog: CHANGELOG.md.
Shipped by the MiniStack community. Contributions credited throughout. GitHub · r/ministack