Skip to main content

Free, open-source local AWS emulator — drop-in LocalStack replacement

Project description

MiniStack — Free, Open-Source Local AWS Emulator

LocalStack is no longer free. MiniStack is a fully open-source, zero-cost drop-in replacement. Single port · No account · No license key · No telemetry · Just AWS APIs, locally.

GitHub release Build Docker Pulls Docker Image Size License Python GitHub stars

MiniStack in action


Why MiniStack?

LocalStack recently moved its core services behind a paid plan. If you relied on LocalStack Community for local development and CI/CD pipelines, MiniStack is your free alternative.

  • 23 AWS services emulated on a single port (4566)
  • Drop-in compatible — works with boto3, AWS CLI, Terraform, CDK, Pulumi, any SDK
  • Real infrastructure — RDS spins up actual Postgres/MySQL containers, ElastiCache spins up real Redis, Athena runs real SQL via DuckDB, ECS runs real Docker containers
  • Tiny footprint — ~150MB image, ~30MB RAM at idle vs LocalStack's ~1GB image and ~500MB RAM
  • Fast startup — under 2 seconds
  • MIT licensed — use it, fork it, contribute to it

Quick Start

# Option 1: Docker Hub (recommended)
docker run -p 4566:4566 nahuelnucera/ministack

# Option 2: Clone and build
git clone https://github.com/Nahuel990/ministack
cd ministack

# Start with Docker Compose (includes Redis sidecar)
docker compose up -d

# Verify
curl http://localhost:4566/_localstack/health

That's it. No account, no API key, no sign-up.


Using with AWS CLI

# Configure a local profile (one-time)
aws configure --profile local
# AWS Access Key ID: test
# AWS Secret Access Key: test
# Default region: us-east-1
# Default output format: json

# Use --endpoint-url on any command
aws --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url=http://localhost:4566 s3 cp ./file.txt s3://my-bucket/
aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name my-queue
aws --endpoint-url=http://localhost:4566 dynamodb list-tables
aws --endpoint-url=http://localhost:4566 sts get-caller-identity

# Or set the endpoint globally for a session
export AWS_ENDPOINT_URL=http://localhost:4566
aws s3 ls
aws sqs list-queues

awslocal wrapper

chmod +x bin/awslocal
./bin/awslocal s3 ls
./bin/awslocal dynamodb list-tables

Using with boto3

import boto3

# All clients use the same endpoint
def client(service):
    return boto3.client(
        service,
        endpoint_url="http://localhost:4566",
        aws_access_key_id="test",
        aws_secret_access_key="test",
        region_name="us-east-1",
    )

# S3
s3 = client("s3")
s3.create_bucket(Bucket="my-bucket")
s3.put_object(Bucket="my-bucket", Key="hello.txt", Body=b"Hello, MiniStack!")
obj = s3.get_object(Bucket="my-bucket", Key="hello.txt")
print(obj["Body"].read())  # b'Hello, MiniStack!'

# SQS
sqs = client("sqs")
q = sqs.create_queue(QueueName="my-queue")
sqs.send_message(QueueUrl=q["QueueUrl"], MessageBody="hello")
msgs = sqs.receive_message(QueueUrl=q["QueueUrl"])
print(msgs["Messages"][0]["Body"])  # hello

# DynamoDB
ddb = client("dynamodb")
ddb.create_table(
    TableName="Users",
    KeySchema=[{"AttributeName": "userId", "KeyType": "HASH"}],
    AttributeDefinitions=[{"AttributeName": "userId", "AttributeType": "S"}],
    BillingMode="PAY_PER_REQUEST",
)
ddb.put_item(TableName="Users", Item={"userId": {"S": "u1"}, "name": {"S": "Alice"}})

# SSM Parameter Store
ssm = client("ssm")
ssm.put_parameter(Name="/app/db/host", Value="localhost", Type="String")
param = ssm.get_parameter(Name="/app/db/host")
print(param["Parameter"]["Value"])  # localhost

# Secrets Manager
sm = client("secretsmanager")
sm.create_secret(Name="db-password", SecretString='{"password":"s3cr3t"}')

# Kinesis
kin = client("kinesis")
kin.create_stream(StreamName="events", ShardCount=1)
kin.put_record(StreamName="events", Data=b'{"event":"click"}', PartitionKey="user1")

# EventBridge
eb = client("events")
eb.put_events(Entries=[{
    "Source": "myapp",
    "DetailType": "UserSignup",
    "Detail": '{"userId": "123"}',
    "EventBusName": "default",
}])

# Step Functions
sfn = client("stepfunctions")
sfn.create_state_machine(
    name="my-workflow",
    definition='{"StartAt":"Hello","States":{"Hello":{"Type":"Pass","End":true}}}',
    roleArn="arn:aws:iam::000000000000:role/role",
)

Supported Services

Core Services

Service Operations Notes
S3 CreateBucket, DeleteBucket, ListBuckets, HeadBucket, PutObject, GetObject, DeleteObject, HeadObject, CopyObject, ListObjects v1/v2, DeleteObjects, GetBucketVersioning, PutBucketVersioning, GetBucketEncryption, PutBucketEncryption, DeleteBucketEncryption, GetBucketLifecycleConfiguration, PutBucketLifecycleConfiguration, DeleteBucketLifecycle, GetBucketCors, PutBucketCors, DeleteBucketCors, GetBucketAcl, PutBucketAcl, GetBucketTagging, PutBucketTagging, DeleteBucketTagging, GetBucketPolicy, PutBucketPolicy, DeleteBucketPolicy, GetBucketNotificationConfiguration, PutBucketNotificationConfiguration, GetBucketLogging, PutBucketLogging, ListObjectVersions, CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload Optional disk persistence via S3_PERSIST=1
SQS CreateQueue, DeleteQueue, ListQueues, GetQueueUrl, GetQueueAttributes, SetQueueAttributes, PurgeQueue, SendMessage, ReceiveMessage, DeleteMessage, ChangeMessageVisibility, ChangeMessageVisibilityBatch, SendMessageBatch, DeleteMessageBatch, TagQueue, UntagQueue, ListQueueTags Both Query API and JSON protocol; FIFO queues with deduplication; DLQ support
SNS CreateTopic, DeleteTopic, ListTopics, GetTopicAttributes, SetTopicAttributes, Subscribe, Unsubscribe, ListSubscriptions, ListSubscriptionsByTopic, GetSubscriptionAttributes, SetSubscriptionAttributes, ConfirmSubscription, Publish, PublishBatch, TagResource, UntagResource, ListTagsForResource, CreatePlatformApplication, CreatePlatformEndpoint SNS→SQS fanout delivery; SNS→Lambda fanout (synchronous invocation)
DynamoDB CreateTable, UpdateTable, DeleteTable, DescribeTable, ListTables, PutItem, GetItem, DeleteItem, UpdateItem, Query, Scan, BatchWriteItem, BatchGetItem, TransactWriteItems, TransactGetItems, DescribeTimeToLive, UpdateTimeToLive, DescribeContinuousBackups, UpdateContinuousBackups, DescribeEndpoints, TagResource, UntagResource, ListTagsOfResource TTL enforced via background reaper (60s cadence)
Lambda CreateFunction, DeleteFunction, GetFunction, ListFunctions, Invoke, UpdateFunctionCode, UpdateFunctionConfiguration, AddPermission, RemovePermission, ListVersionsByFunction, PublishVersion, TagResource, UntagResource, ListTags, CreateEventSourceMapping, DeleteEventSourceMapping, GetEventSourceMapping, ListEventSourceMappings, UpdateEventSourceMapping, CreateFunctionUrlConfig, GetFunctionUrlConfig, UpdateFunctionUrlConfig, DeleteFunctionUrlConfig, ListFunctionUrlConfigs Python runtimes actually execute with warm worker pool; SQS event source mapping; Function URL CRUD
IAM CreateUser, GetUser, ListUsers, DeleteUser, CreateRole, GetRole, ListRoles, DeleteRole, CreatePolicy, GetPolicy, DeletePolicy, AttachRolePolicy, DetachRolePolicy, PutRolePolicy, GetRolePolicy, DeleteRolePolicy, ListRolePolicies, ListAttachedRolePolicies, CreateAccessKey, ListAccessKeys, DeleteAccessKey, CreateInstanceProfile, GetInstanceProfile, DeleteInstanceProfile, AddRoleToInstanceProfile, RemoveRoleFromInstanceProfile, ListInstanceProfiles, CreateGroup, GetGroup, AddUserToGroup, RemoveUserFromGroup, CreateServiceLinkedRole, CreateOpenIDConnectProvider, TagRole, UntagRole, TagUser, UntagUser, TagPolicy, UntagPolicy
STS GetCallerIdentity, AssumeRole, GetSessionToken
SecretsManager CreateSecret, GetSecretValue, ListSecrets, DeleteSecret, UpdateSecret, DescribeSecret, PutSecretValue, RestoreSecret, RotateSecret, GetRandomPassword, ListSecretVersionIds, TagResource, UntagResource, PutResourcePolicy, GetResourcePolicy, DeleteResourcePolicy, ValidateResourcePolicy
CloudWatch Logs CreateLogGroup, DeleteLogGroup, DescribeLogGroups, CreateLogStream, DeleteLogStream, DescribeLogStreams, PutLogEvents, GetLogEvents, FilterLogEvents, PutRetentionPolicy, DeleteRetentionPolicy, PutSubscriptionFilter, DeleteSubscriptionFilter, DescribeSubscriptionFilters, PutMetricFilter, DeleteMetricFilter, DescribeMetricFilters, TagLogGroup, UntagLogGroup, ListTagsLogGroup, TagResource, UntagResource, ListTagsForResource, StartQuery, GetQueryResults, StopQuery, PutDestination, DeleteDestination, DescribeDestinations

Extended Services

Service Operations Notes
SSM Parameter Store PutParameter, GetParameter, GetParameters, GetParametersByPath, DeleteParameter, DeleteParameters, DescribeParameters, GetParameterHistory, LabelParameterVersion, AddTagsToResource, RemoveTagsFromResource, ListTagsForResource Supports String, SecureString, StringList
EventBridge CreateEventBus, DeleteEventBus, ListEventBuses, PutRule, DeleteRule, ListRules, DescribeRule, EnableRule, DisableRule, PutTargets, RemoveTargets, ListTargetsByRule, PutEvents, TagResource, UntagResource, ListTagsForResource, CreateArchive, DeleteArchive, DescribeArchive, ListArchives, PutPermission, RemovePermission, CreateConnection, DescribeConnection, DeleteConnection, ListConnections, CreateApiDestination, DescribeApiDestination, DeleteApiDestination, ListApiDestinations Lambda target dispatch on PutEvents
Kinesis CreateStream, DeleteStream, DescribeStream, ListStreams, ListShards, PutRecord, PutRecords, GetShardIterator, GetRecords, MergeShards, SplitShard, UpdateShardCount, StartStreamEncryption, StopStreamEncryption, EnableEnhancedMonitoring, DisableEnhancedMonitoring, RegisterStreamConsumer, DeregisterStreamConsumer, ListStreamConsumers, DescribeStreamConsumer, AddTagsToStream, RemoveTagsFromStream, ListTagsForStream Partition key → shard routing
CloudWatch Metrics PutMetricData, GetMetricStatistics, GetMetricData, ListMetrics, PutMetricAlarm, PutCompositeAlarm, DescribeAlarms, DescribeAlarmsForMetric, DescribeAlarmHistory, DeleteAlarms, SetAlarmState, EnableAlarmActions, DisableAlarmActions, TagResource, UntagResource, ListTagsForResource, PutDashboard, GetDashboard, DeleteDashboards, ListDashboards CBOR and JSON protocol
SES SendEmail, SendRawEmail, SendTemplatedEmail, SendBulkTemplatedEmail, VerifyEmailIdentity, VerifyEmailAddress, VerifyDomainIdentity, VerifyDomainDkim, ListIdentities, GetIdentityVerificationAttributes, GetIdentityDkimAttributes, DeleteIdentity, GetSendQuota, GetSendStatistics, CreateConfigurationSet, DeleteConfigurationSet, DescribeConfigurationSet, ListConfigurationSets, CreateTemplate, GetTemplate, UpdateTemplate, DeleteTemplate, ListTemplates Emails stored in-memory, not sent
Step Functions CreateStateMachine, DeleteStateMachine, DescribeStateMachine, UpdateStateMachine, ListStateMachines, StartExecution, StartSyncExecution, StopExecution, DescribeExecution, DescribeStateMachineForExecution, ListExecutions, GetExecutionHistory, SendTaskSuccess, SendTaskFailure, SendTaskHeartbeat, TagResource, UntagResource, ListTagsForResource Full ASL interpreter; Retry/Catch; waitForTaskToken; Pass/Task/Choice/Wait/Succeed/Fail/Map/Parallel
API Gateway v2 CreateApi, GetApi, GetApis, UpdateApi, DeleteApi, CreateRoute, GetRoute, GetRoutes, UpdateRoute, DeleteRoute, CreateIntegration, GetIntegration, GetIntegrations, UpdateIntegration, DeleteIntegration, CreateStage, GetStage, GetStages, UpdateStage, DeleteStage, CreateDeployment, GetDeployment, GetDeployments, DeleteDeployment, CreateAuthorizer, GetAuthorizer, GetAuthorizers, UpdateAuthorizer, DeleteAuthorizer, TagResource, UntagResource, GetTags HTTP API (v2) protocol; Lambda proxy (AWS_PROXY) and HTTP proxy (HTTP_PROXY) integrations; data plane via {apiId}.execute-api.localhost; {param} and {proxy+} path matching; JWT/Lambda authorizer CRUD
API Gateway v1 CreateRestApi, GetRestApi, GetRestApis, UpdateRestApi, DeleteRestApi, CreateResource, GetResource, GetResources, UpdateResource, DeleteResource, PutMethod, GetMethod, DeleteMethod, UpdateMethod, PutMethodResponse, GetMethodResponse, DeleteMethodResponse, PutIntegration, GetIntegration, DeleteIntegration, UpdateIntegration, PutIntegrationResponse, GetIntegrationResponse, DeleteIntegrationResponse, CreateDeployment, GetDeployment, GetDeployments, UpdateDeployment, DeleteDeployment, CreateStage, GetStage, GetStages, UpdateStage, DeleteStage, CreateAuthorizer, GetAuthorizer, GetAuthorizers, UpdateAuthorizer, DeleteAuthorizer, CreateModel, GetModel, GetModels, DeleteModel, CreateApiKey, GetApiKey, GetApiKeys, UpdateApiKey, DeleteApiKey, CreateUsagePlan, GetUsagePlan, GetUsagePlans, UpdateUsagePlan, DeleteUsagePlan, CreateUsagePlanKey, GetUsagePlanKeys, DeleteUsagePlanKey, CreateDomainName, GetDomainName, GetDomainNames, DeleteDomainName, CreateBasePathMapping, GetBasePathMapping, GetBasePathMappings, DeleteBasePathMapping, TagResource, UntagResource, GetTags REST API (v1) protocol; Lambda proxy format 1.0 (AWS_PROXY), HTTP proxy (HTTP_PROXY), MOCK integration; data plane via {apiId}.execute-api.localhost; resource tree with {param} and {proxy+} path matching; JSON Patch for all PATCH operations; state persistence

Infrastructure Services (with real Docker execution)

Service Operations Real Execution
ECS CreateCluster, UpdateCluster, DeleteCluster, DescribeClusters, ListClusters, RegisterTaskDefinition, DeregisterTaskDefinition, DescribeTaskDefinition, ListTaskDefinitions, CreateService, DeleteService, DescribeServices, UpdateService, ListServices, RunTask, StopTask, DescribeTasks, ListTasks, CreateCapacityProvider, DeleteCapacityProvider, DescribeCapacityProviders, PutClusterCapacityProviders, TagResource, UntagResource, ListTagsForResource RunTask starts real Docker containers via Docker socket
RDS CreateDBInstance, DeleteDBInstance, DescribeDBInstances, StartDBInstance, StopDBInstance, RebootDBInstance, ModifyDBInstance, CreateDBCluster, DeleteDBCluster, DescribeDBClusters, StartDBCluster, StopDBCluster, CreateDBSubnetGroup, DescribeDBSubnetGroups, ModifyDBSubnetGroup, DeleteDBSubnetGroup, CreateDBParameterGroup, DescribeDBParameterGroups, ModifyDBParameterGroup, DeleteDBParameterGroup, DescribeDBParameters, CreateDBClusterParameterGroup, DescribeDBEngineVersions, DescribeOrderableDBInstanceOptions, CreateDBSnapshot, DeleteDBSnapshot, DescribeDBSnapshots, CreateDBClusterSnapshot, DeleteDBClusterSnapshot, DescribeDBClusterSnapshots, CreateDBInstanceReadReplica, RestoreDBInstanceFromDBSnapshot, CreateOptionGroup, DescribeOptionGroups, AddTagsToResource, RemoveTagsFromResource, ListTagsForResource CreateDBInstance spins up real Postgres/MySQL Docker container, returns actual host:port endpoint
ElastiCache CreateCacheCluster, DeleteCacheCluster, DescribeCacheClusters, ModifyCacheCluster, RebootCacheCluster, CreateReplicationGroup, DeleteReplicationGroup, DescribeReplicationGroups, ModifyReplicationGroup, IncreaseReplicaCount, DecreaseReplicaCount, CreateCacheSubnetGroup, DescribeCacheSubnetGroups, ModifyCacheSubnetGroup, DeleteCacheSubnetGroup, CreateCacheParameterGroup, DescribeCacheParameterGroups, ModifyCacheParameterGroup, ResetCacheParameterGroup, DeleteCacheParameterGroup, DescribeCacheParameters, DescribeCacheEngineVersions, CreateUser, DescribeUsers, DeleteUser, ModifyUser, CreateUserGroup, DescribeUserGroups, DeleteUserGroup, ModifyUserGroup, CreateSnapshot, DeleteSnapshot, DescribeSnapshots, DescribeEvents CreateCacheCluster spins up real Redis/Memcached Docker container
Glue CreateDatabase, DeleteDatabase, GetDatabase, GetDatabases, CreateTable, DeleteTable, GetTable, GetTables, UpdateTable, BatchDeleteTable, CreatePartition, GetPartitions, BatchCreatePartition, BatchGetPartition, CreatePartitionIndex, GetPartitionIndexes, CreateConnection, GetConnections, CreateCrawler, UpdateCrawler, GetCrawler, GetCrawlerMetrics, StartCrawler, CreateJob, GetJob, GetJobs, StartJobRun, GetJobRun, GetJobRuns, CreateTrigger, GetTrigger, DeleteTrigger, UpdateTrigger, StartTrigger, StopTrigger, ListTriggers, GetTriggers, CreateWorkflow, GetWorkflow, DeleteWorkflow, UpdateWorkflow, StartWorkflowRun, CreateSecurityConfiguration, GetSecurityConfiguration, GetSecurityConfigurations, DeleteSecurityConfiguration, CreateClassifier, GetClassifier, GetClassifiers, DeleteClassifier, TagResource, UntagResource, GetTags Python shell jobs actually execute via subprocess
Athena StartQueryExecution, GetQueryExecution, GetQueryResults, StopQueryExecution, ListQueryExecutions, BatchGetQueryExecution, CreateWorkGroup, DeleteWorkGroup, GetWorkGroup, ListWorkGroups, UpdateWorkGroup, CreateNamedQuery, DeleteNamedQuery, GetNamedQuery, ListNamedQueries, BatchGetNamedQuery, CreateDataCatalog, GetDataCatalog, ListDataCatalogs, DeleteDataCatalog, UpdateDataCatalog, CreatePreparedStatement, GetPreparedStatement, DeletePreparedStatement, ListPreparedStatements, GetTableMetadata, ListTableMetadata, TagResource, UntagResource, ListTagsForResource Real SQL via DuckDB when installed (pip install duckdb), otherwise returns mock results; result pagination; column type metadata
Firehose CreateDeliveryStream, DeleteDeliveryStream, DescribeDeliveryStream, ListDeliveryStreams, PutRecord, PutRecordBatch, UpdateDestination, TagDeliveryStream, UntagDeliveryStream, ListTagsForDeliveryStream, StartDeliveryStreamEncryption, StopDeliveryStreamEncryption S3 destinations write records to the local S3 emulator; all other destination types buffer in-memory; concurrency-safe UpdateDestination via VersionId

Real Database Endpoints (RDS)

When you create an RDS instance, MiniStack starts a real database container and returns the actual connection endpoint:

import boto3
import psycopg2  # pip install psycopg2-binary

rds = boto3.client("rds", endpoint_url="http://localhost:4566",
                   aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")

resp = rds.create_db_instance(
    DBInstanceIdentifier="mydb",
    DBInstanceClass="db.t3.micro",
    Engine="postgres",
    MasterUsername="admin",
    MasterUserPassword="password",
    DBName="appdb",
    AllocatedStorage=20,
)

endpoint = resp["DBInstance"]["Endpoint"]
# Connect directly — it's a real Postgres instance
conn = psycopg2.connect(
    host=endpoint["Address"],   # localhost
    port=endpoint["Port"],      # 15432 (auto-assigned)
    user="admin",
    password="password",
    dbname="appdb",
)

Supported engines: postgres, mysql, mariadb, aurora-postgresql, aurora-mysql


Real Redis Endpoints (ElastiCache)

import boto3
import redis  # pip install redis

ec = boto3.client("elasticache", endpoint_url="http://localhost:4566",
                  aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")

resp = ec.create_cache_cluster(
    CacheClusterId="my-redis",
    Engine="redis",
    CacheNodeType="cache.t3.micro",
    NumCacheNodes=1,
)

node = resp["CacheCluster"]["CacheNodes"][0]["Endpoint"]
r = redis.Redis(host=node["Address"], port=node["Port"])
r.set("key", "value")
print(r.get("key"))  # b'value'

A Redis sidecar is also always available at localhost:6379 when using Docker Compose.


Athena with Real SQL

Athena queries run via DuckDB and can query files in your local S3 data directory:

import boto3, time

athena = boto3.client("athena", endpoint_url="http://localhost:4566",
                      aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")

# Query runs real SQL via DuckDB
resp = athena.start_query_execution(
    QueryString="SELECT 42 AS answer, 'hello' AS greeting",
    ResultConfiguration={"OutputLocation": "s3://athena-results/"},
)
query_id = resp["QueryExecutionId"]

# Poll for result
while True:
    status = athena.get_query_execution(QueryExecutionId=query_id)
    if status["QueryExecution"]["Status"]["State"] == "SUCCEEDED":
        break
    time.sleep(0.1)

results = athena.get_query_results(QueryExecutionId=query_id)
for row in results["ResultSet"]["Rows"][1:]:  # skip header
    print([col["VarCharValue"] for col in row["Data"]])
# ['42', 'hello']

ECS with Real Containers

import boto3

ecs = boto3.client("ecs", endpoint_url="http://localhost:4566",
                   aws_access_key_id="test", aws_secret_access_key="test", region_name="us-east-1")

ecs.create_cluster(clusterName="dev")

ecs.register_task_definition(
    family="web",
    containerDefinitions=[{
        "name": "nginx",
        "image": "nginx:alpine",
        "cpu": 128,
        "memory": 256,
        "portMappings": [{"containerPort": 80, "hostPort": 8080}],
    }],
)

# This actually runs an nginx container via Docker
resp = ecs.run_task(cluster="dev", taskDefinition="web", count=1)
task_arn = resp["tasks"][0]["taskArn"]

# Stop it (removes the container)
ecs.stop_task(cluster="dev", task=task_arn)

Configuration

Variable Default Description
GATEWAY_PORT 4566 Port to listen on
MINISTACK_HOST localhost Hostname used in SQS queue URLs returned to clients
LOG_LEVEL INFO DEBUG, INFO, WARNING, ERROR
S3_PERSIST 0 Set 1 to persist S3 objects to disk
S3_DATA_DIR /tmp/localstack-data/s3 S3 persistence directory
REDIS_HOST redis Redis host for ElastiCache fallback
REDIS_PORT 6379 Redis port for ElastiCache fallback
RDS_BASE_PORT 15432 Starting host port for RDS containers
ELASTICACHE_BASE_PORT 16379 Starting host port for ElastiCache containers
PERSIST_STATE 0 Set 1 to persist service state across restarts
STATE_DIR /tmp/ministack-state Directory for persisted state files

State Persistence

When PERSIST_STATE=1, MiniStack saves service state to STATE_DIR on shutdown and reloads it on startup. Writes are atomic (write-to-tmp then rename) to prevent corruption on crash.

Services currently supporting persistence: API Gateway v1, API Gateway v2

docker run -p 4566:4566 \
  -e PERSIST_STATE=1 \
  -e STATE_DIR=/data/ministack-state \
  -v /tmp/ministack-data:/data \
  nahuelnucera/ministack

Lambda Warm Starts

MiniStack keeps Python Lambda functions warm between invocations. After the first call (cold start), the handler module stays imported in a persistent subprocess. Subsequent calls skip the import step, matching real AWS warm-start behaviour and making test suites significantly faster.


Architecture

                    ┌─────────────────────────────────────────┐
 AWS CLI / boto3    │         ASGI Gateway  :4566             │
 Terraform / CDK ──►│  ┌───────────────────────────────────┐  │
 Any AWS SDK        │  │          Request Router            │  │
                    │  │  1. X-Amz-Target header            │  │
                    │  │  2. Authorization credential scope  │  │
                    │  │  3. Action query param              │  │
                    │  │  4. URL path pattern                │  │
                    │  │  5. Host header                     │  │
                    │  │  6. Default → S3                    │  │
                    │  └──────────────┬────────────────────┘  │
                    │                 │                        │
                    │  ┌──────────────▼──────────────────┐    │
                    │  │         Service Handlers         │    │
                    │  │                                  │    │
                    │  │  S3   SQS   SNS   DynamoDB       │    │
                    │  │  Lambda  IAM  STS  Secrets       │    │
                    │  │  SSM  EventBridge  Kinesis        │    │
                    │  │  CloudWatch  SES  StepFunctions  │    │
                    │  │  ECS   RDS   ElastiCache          │    │
                    │  │  Glue  Athena  API Gateway        │    │
                    │  └──────────────────────────────────┘    │
                    │                                          │
                    │  In-Memory Storage + Optional Docker     │
                    └─────────────────────────────────────────┘
                                        │
                         ┌──────────────┼──────────────┐
                         ▼              ▼              ▼
                    Redis:6379    Postgres:15432+  MySQL:15433+
                    (ElastiCache)    (RDS)           (RDS)

Running Tests

# Install test dependencies
pip install boto3 pytest duckdb docker cbor2

# Start MiniStack
docker compose up -d

# Run the full test suite (450 tests across all 23 services)
pytest tests/ -v

Expected output:

collected 450 items

tests/test_services.py::test_s3_create_bucket PASSED
...
tests/test_services.py::test_apigwv1_execute_mock_response_parameters PASSED

450 passed in ~60s

Terraform / CDK / Pulumi

Terraform

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    s3             = "http://localhost:4566"
    sqs            = "http://localhost:4566"
    dynamodb       = "http://localhost:4566"
    lambda         = "http://localhost:4566"
    iam            = "http://localhost:4566"
    sts            = "http://localhost:4566"
    secretsmanager = "http://localhost:4566"
    ssm            = "http://localhost:4566"
    kinesis        = "http://localhost:4566"
    sns            = "http://localhost:4566"
    rds            = "http://localhost:4566"
    ecs            = "http://localhost:4566"
    glue           = "http://localhost:4566"
    athena         = "http://localhost:4566"
    elasticache    = "http://localhost:4566"
    stepfunctions  = "http://localhost:4566"
    cloudwatch     = "http://localhost:4566"
    logs           = "http://localhost:4566"
    events         = "http://localhost:4566"
    ses            = "http://localhost:4566"
    apigateway     = "http://localhost:4566"
  }
}

AWS CDK

// cdk.json or in your app
const app = new cdk.App();
// Set endpoint override via environment
process.env.AWS_ENDPOINT_URL = "http://localhost:4566";

Pulumi

# Pulumi.dev.yaml
config:
  aws:endpoints:
    - s3: http://localhost:4566
      dynamodb: http://localhost:4566
      # ... etc

Comparison

Feature MiniStack LocalStack Free LocalStack Pro
S3, SQS, SNS, DynamoDB
Lambda (Python execution)
IAM, STS, SecretsManager
CloudWatch Logs
SSM Parameter Store
EventBridge
Kinesis
SES
Step Functions
RDS (real DB containers)
ElastiCache (real Redis)
ECS (real Docker containers)
Athena (real SQL via DuckDB)
Glue Data Catalog + Jobs
API Gateway v2 (HTTP API)
API Gateway v1 (REST API)
Firehose
Cognito
CloudFormation partial
Cost Free Was free, now paid $35+/mo
Docker image size ~150MB ~1GB ~1GB
Memory at idle ~30MB ~500MB ~500MB
Startup time ~2s ~15-30s ~15-30s
License MIT BSL (restricted) Proprietary

Contributing

PRs welcome. The codebase is intentionally simple — each service is a single self-contained Python file in services/. Adding a new service means:

  1. Create services/myservice.py with an async def handle_request(...) function
  2. Add it to SERVICE_HANDLERS in app.py
  3. Add detection patterns to core/router.py
  4. Add tests to tests/test_services.py

License

MIT — free to use, modify, and distribute. No restrictions.

Copyright (c) 2024 MiniStack Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ministack-1.0.7.tar.gz (253.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ministack-1.0.7-py3-none-any.whl (206.9 kB view details)

Uploaded Python 3

File details

Details for the file ministack-1.0.7.tar.gz.

File metadata

  • Download URL: ministack-1.0.7.tar.gz
  • Upload date:
  • Size: 253.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for ministack-1.0.7.tar.gz
Algorithm Hash digest
SHA256 69abfaff0750e346b14e23f54489bc1f5ab00cf308d5c49d5e581900920cb3e3
MD5 eed4f98b3cdf02c67707d42eb882ebb1
BLAKE2b-256 b905b887ca7bd24b189162bbb124ab3a2922d24fefe3a7e934651b34d3836967

See more details on using hashes here.

File details

Details for the file ministack-1.0.7-py3-none-any.whl.

File metadata

  • Download URL: ministack-1.0.7-py3-none-any.whl
  • Upload date:
  • Size: 206.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for ministack-1.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 5e06690b58c413cd080df017f959ef978f85da1da97e5953d7b56c8ffa8120be
MD5 95faec5359f3f182e6edfe22526a6a81
BLAKE2b-256 3855ed8cc01804b6059a6b79031bb703f203d0a6d82efc4b91dcdd7cf50d38c1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page