DocsAWS 101BlogServices

Configuration

All MiniStack configuration is driven by environment variables and a small admin HTTP surface. This page is the consolidated reference.

Source of truth: env-var names and defaults are extracted from the MiniStack 1.5.8 codebase (ministack/app.py, ministack/core/*, and per-service modules). Search os.environ / os.getenv in the repo to find the authoritative site where each is read.

Docker images (slim vs full)

MiniStack publishes two image variants. The default (slim) image is what ministackorg/ministack and :latest resolve to; pull the full image only when you need one of the extra engines it bundles.

Slim (default)Full
Tagsministackorg/ministack, :latest, :1.5.8ministackorg/ministack:full, :1.5.8-full
Basepython:3.13-alpine (musl libc)python:3.13-slim (Debian, glibc)
Size~280 MB~440 MB
edition on /_ministack/healthlightfull

The full image bundles the optional [full] dependencies (duckdb, cryptography, psycopg2-binary, pymysql, asyncssh, aws-sam-translator), which enable:

  • Athena — real SQL execution via DuckDB.
  • S3 Tables & Firehose Iceberg delivery — the DuckDB-backed Iceberg REST catalog engine.
  • CloudFormation SAM transformTransform: AWS::Serverless-2016-10-31 expansion via aws-sam-translator.
  • IoT — certificate issuance and the in-process Local CA (cryptography).
  • Databases — native PostgreSQL / MySQL driver access, e.g. the RDS Data API (psycopg2 / pymysql).
  • Transfer Family — the SFTP data plane (asyncssh).

On the slim image, a call that needs one of these returns a clear error pointing you at the full image rather than failing silently. Everything else — S3, DynamoDB, SQS, SNS, Lambda, EC2, CloudFormation, and the rest — runs identically on both.

# slim (default)
docker run -p 4566:4566 ministackorg/ministack

# full — Athena, Iceberg, SAM, IoT certs, DB drivers, SFTP
docker run -p 4566:4566 ministackorg/ministack:full

General

VariableDefaultPurpose
GATEWAY_PORT4566Port the single ASGI gateway listens on. Fallback order: GATEWAY_PORTEDGE_PORT → 4566.
EDGE_PORT(fallback)Legacy alias honored when GATEWAY_PORT is unset.
MINISTACK_HOSTlocalhostHostname the server advertises (used in returned endpoints).
MINISTACK_REGIONus-east-1Default AWS region for callers who don't sign requests with SigV4.
LOG_LEVELINFODEBUG, INFO, WARNING, ERROR. DEBUG logs every request's routing decision.
MINISTACK_WORKER_THREADS64Size of the thread pool executor used for sync-in-async offload and Lambda local runs.
SERVICES(all enabled)Comma-separated filter, e.g. s3,sqs,lambda. Only those services answer — good for shrinking startup in tiny test matrices.
BIND_HOST0.0.0.0Interface the gateway binds to.
MINISTACK_ACCOUNT_ID000000000000Fallback account ID for callers whose access key doesn't encode a 12-digit account.
MINISTACK_HOSTNAME(system hostname)Overrides the host half of the host:port instance identity used to label and reap MiniStack-spawned containers — set it when multiple MiniStack instances share one Docker daemon.

TLS

The gateway can speak HTTPS natively — useful for AWS SDKs that hardcode https:// against Cognito Hosted UI / Amplify v6 endpoints, without a separate TLS-terminating proxy.

VariableDefaultPurpose
USE_SSL0When 1 (also accepts true / yes), the listener serves TLS instead of plaintext HTTP on the same port. Flag name aligns with LocalStack's USE_SSL for drop-in compose.yml swaps.
MINISTACK_SSL_CERT(auto-generated)Path to a PEM-encoded certificate. If unset, MiniStack auto-generates a self-signed RSA cert (CN: ministack-local, SAN: localhost, ministack, 127.0.0.1, ::1) cached under ${TMPDIR}/ministack-tls/ so the cert survives restarts. Pin to an mkcert-issued cert for browser trust.
MINISTACK_SSL_KEY(auto-generated)Path to the PEM-encoded private key matching MINISTACK_SSL_CERT. Auto-generation shells out to the openssl CLI (already present in both Docker images) — no Python crypto dependency added.

Authorization (IAM)

By default MiniStack does not enforce IAM — it accepts any credentials and authorizes every call, which keeps local-dev and test flows friction-free. Opt in to policy evaluation when you want to exercise least-privilege behavior.

VariableDefaultPurpose
AUTHfalseWhen true, MiniStack evaluates the caller's IAM policies before serving a request and returns 403 AccessDenied (User: {arn} is not authorized to perform: {action}) when they do not allow it, across control-plane and data-plane paths (S3 object access, execute-api:Invoke, lambda:InvokeFunctionUrl, and per-service actions resolved from the botocore model). SigV4 signatures are still not validated — the access key only identifies the principal — so this is authorization, not authentication.
MINISTACK_AUTOCREATE_AWS_MANAGED0By default an unknown AWS-managed policy ARN returns NoSuchEntity, as on real AWS. Set 1 to auto-create it on reference instead — useful for Terraform stacks that attach less common AWS-managed policies.

Persistence

Three independent layers. Enable whatever subset fits your workflow.

VariableDefaultPurpose
PERSIST_STATE / LOCALSTACK_PERSISTENCE0Master switch. When 1, every multi-tenant service snapshots its in-memory maps to STATE_DIR on shutdown and restores on startup.
STATE_DIR/tmp/ministack-stateDirectory for JSON snapshots. Mount to a host volume in Docker to survive container recreation.
S3_PERSIST0 (auto-enabled if LOCALSTACK_PERSISTENCE=1)Writes S3 object bytes (the body, not just metadata) to disk.
S3_DATA_DIR/tmp/ministack-data/s3Where S3 object files are stored. Mount this for persistent object storage.
RDS_PERSIST0Switches RDS Docker containers from tmpfs to Docker named volumes. Real databases, real persistence.
RDS_TMPFS_SIZE256mSize of the tmpfs mount when RDS_PERSIST=0. Bump to 1g or 2g if tests hit "no space left on device".
DSQL_PERSIST0Switches Aurora DSQL's backing Postgres containers from tmpfs to Docker named volumes (requires DSQL_STRICT=1). Real data, real persistence.
MWAA_PERSIST0When 1, MWAA Airflow containers mount named Docker volumes for the DAGs folder and metadata DB instead of ephemeral storage.
Common setup for local development: enable PERSIST_STATE, S3_PERSIST, and RDS_PERSIST — your entire state survives restarts. For CI: leave all three at 0; every run starts clean, fast, and deterministic.

Nested containers (RDS / EKS / ElastiCache / Lambda)

Services that spawn real sidecar containers share a common networking + registry-prefix surface.

VariableDefaultPurpose
DOCKER_NETWORK(unset)Attaches every container-backed service to the named Docker network. RDS/ElastiCache endpoints return the routable in-network IP instead of localhost.
LAMBDA_DOCKER_NETWORK(falls back to DOCKER_NETWORK)Lambda-scoped override. Legacy — prefer DOCKER_NETWORK.
MINISTACK_IMAGE_PREFIX(unset)Private-registry prefix prepended to every nested image (postgres, mysql, mariadb, redis, memcached, k3s, Lambda runtimes under public.ecr.aws/lambda/*). Idempotent on already-prefixed images. The Testcontainers Java module auto-forwards hub.image.name.prefix into this variable.
RDS_BASE_PORT15432First host port allocated to an RDS container. Each subsequent DB gets the next free port.
ELASTICACHE_BASE_PORT16379First host port for ElastiCache containers.
ELASTICACHE_CLUSTER_MODE_REAL0When 1 (requires DOCKER_NETWORK), CreateReplicationGroup with NumNodeGroups=N / ReplicasPerNodeGroup=R provisions N × (1 + R) cluster-enabled Redis nodes wired with redis-cli --cluster create. Cluster-aware clients see real CLUSTER SLOTS / MOVED redirects.
OPENSEARCH_DATAPLANE0When 1, CreateDomain spawns a real opensearchproject/opensearch container per domain (same pattern as ElastiCache and RDS). DescribeDomain.Endpoint then points at the container, and _cluster/health / _search work end to end. Default 0 returns a stub endpoint (<domain>.ministack.local:9200) for fast offline management-plane tests.
OPENSEARCH_BASE_PORT14571First host port allocated when OPENSEARCH_DATAPLANE=1. Each subsequent CreateDomain gets the next free port.
OPENSEARCH_IMAGEopensearchproject/opensearch:2.15.0Image used when spawning per-domain OpenSearch containers. Override to pin a specific engine version.
OPENSEARCH_DASHBOARDS0Set 1 together with OPENSEARCH_DATAPLANE=1 to also spawn a per-domain opensearchproject/opensearch-dashboards sidecar wired to the cluster. DescribeDomain.DomainStatus.DashboardEndpoint is then populated.
OPENSEARCH_DASHBOARDS_BASE_PORT15601First host port allocated for per-domain Dashboards containers.
OPENSEARCH_DASHBOARDS_IMAGEopensearchproject/opensearch-dashboards:2.15.0Image used when spawning per-domain Dashboards containers.
MINISTACK_OPENSEARCH_ENDPOINT(unset)If set (e.g. localhost:9200), every domain's DescribeDomain.Endpoint resolves to this value and ministack does not spawn per-domain containers. Useful when you bring your own cluster.
DSQL_STRICT0When 1 (with a Docker daemon), each Aurora DSQL cluster is backed by a real postgres container fronted by an in-process wire-protocol proxy that enforces DSQL's SQL subset. Default 0 keeps clusters ACTIVE metadata-only.
DSQL_BASE_PORT25432First host port allocated to a DSQL backend container when DSQL_STRICT=1. A fixed 30-port window off this base caps concurrent backends (one per cluster).
DSQL_PG_IMAGEpostgres:16-alpineImage used for the per-cluster Postgres backend. Override to pin a specific Postgres version.
EKS_BASE_PORT16443First host port for EKS k3s clusters.
EKS_K3S_IMAGErancher/k3s:v1.31.4-k3s1k3s image tag for EKS cluster sidecars. Pin this if you need a specific Kubernetes version.
ECS_REAP_INTERVAL_SECONDS60Interval for the ECS task reaper. Cleans up stopped task containers.
EC2_DOCKER_FLAGS(unset)Docker-CLI-style flags applied to every EC2 instance container launched via RegisterImage (--privileged, --cap-add, -e, -v, --tmpfs, --add-host, -m, --shm-size). --init is refused — instance containers always run with init. Unset, nothing changes.
MWAA_AIRFLOW_IMAGEapache/airflow:3.0.6Image for MWAA environment containers; when unset, the environment's requested AirflowVersion picks apache/airflow:<version>.
MWAA_BASE_PORT18080First host port allocated to an MWAA Airflow webserver container.
MINISTACK_DOCKER_TIMEOUT10Timeout (seconds) for Docker daemon calls made by container-backed services and the container reaper.
MINISTACK_RDS_PUBLIC_ENDPOINT0When 1, DescribeDBInstances endpoints resolve to MINISTACK_HOST + the published host port instead of the container-internal address — for remote MiniStack deployments where clients can't reach the Docker network.
ECS_RESTORE_RECONCILE_DELAY_SECONDS2Delay after a warm boot before ECS reconciles restored task records against the containers actually running.
GLUE_DOCKER_IMAGE(auto)Override the Docker image used for Glue Spark (glueetl) jobs.
GLUE_CRAWLER_RUN_SECONDS5How long a Glue crawler stays RUNNING before completing.

Lambda

VariableDefaultPurpose
LAMBDA_EXECUTORlocallocal runs Python as a subprocess (fast, no Docker). docker runs each invocation in a container, reusing a warm-container pool. Image-based and provided.* runtimes always use Docker regardless.
LAMBDA_STRICT01 forces AWS-fidelity mode — in-process fallback is disabled and missing Docker surfaces as Runtime.DockerUnavailable.
LAMBDA_DOCKER_FLAGS(unset)Extra docker run flags. Whitelisted: -e, -v, --dns, --network, --cap-add, -m, --shm-size, --tmpfs, --add-host, --security-opt, --privileged, --read-only.
CODEBUILD_DOCKER_FLAGS(unset)Extra docker run flags for the CodeBuild local-agent container; same syntax and whitelist as LAMBDA_DOCKER_FLAGS. On an SELinux-enforcing host the agent needs --security-opt label=disable to reach the Docker socket.
LAMBDA_REMOTE_DOCKER_VOLUME_MOUNT(unset)Path remapping for remote Docker daemons (when the daemon can't see the host's /tmp).
LAMBDA_WARM_TTL_SECONDS300 (5 min)Idle container TTL in the warm pool. Raise for long local-dev sessions, lower for CI.
LAMBDA_KEEPALIVE_MS(unset)LocalStack-compat lever (not an AWS behavior). 0 forces a per-invocation cold start for Docker RIE runtimes (Ruby / Java / .NET): the warm container is torn down after each invocation so the next invoke re-runs INIT. Unset or any non-zero value keeps the warm-pool behavior.
LAMBDA_ACCOUNT_CONCURRENCY0Account-level concurrent-invocation cap. 0 = unbounded; set 1000 to simulate AWS's default.
LAMBDA_STATE_TRANSITION_SECONDS0.5Artificial delay between Pending→Active and Active→Inactive. Matches AWS's eventual-consistency window for tests that assert on state.
LAMBDA_ASYNC_RETRY_BASE_SECONDS1Base backoff for async invoke retries.
LAMBDA_ASYNC_RETRY_MAX_SECONDS30Cap on async retry backoff.
_LAMBDA_LAYERS_DIRS(unset)Colon-separated directories scanned for layers. Useful for local-dev layer prototyping.

Databases & analytics

VariableDefaultPurpose
ATHENA_ENGINEautoauto uses DuckDB when installed and falls back to mock. duckdb requires DuckDB (errors if missing). mock returns synthetic rows (echoes SELECT literals/aliases, else a single mock_value row) without real SQL.
athena.ATHENA_DATA_DIR (runtime config, not an env var)(derived from S3_DATA_DIR)DuckDB working directory for Athena query staging. Settable only via POST /_ministack/config; the ATHENA_DATA_DIR environment variable is not read.
SFN_MOCK_CONFIG(unset)Path to a JSON mock file (AWS SFN Local format) for Step Functions Task states.
MINISTACK_DDB_EXPORT_COMPLETE_AFTER_SEC1Seconds a DynamoDB ExportTableToPointInTime job stays IN_PROGRESS before reporting COMPLETED.
MINISTACK_DDB_IMPORT_COMPLETE_AFTER_SEC1Same pacing for ImportTable jobs.

Other services

VariableDefaultPurpose
SMTP_HOST(unset)If set (e.g. mailhog:1025), SES SendEmail/SendRawEmail forwards to a real SMTP server. Otherwise mails are stored in memory and inspectable at /_ministack/ses/messages.
REDIS_HOST / REDIS_PORTredis / 6379External Redis (if you don't want ElastiCache to spin up a container per cluster).
MINISTACK_APIGW_PROXY_TIMEOUT_SECONDS30Timeout for API Gateway HTTP / HTTP_PROXY integration calls to the upstream backend. Bumped per-deployment when an upstream is intentionally slow.
MINISTACK_APIGW_JWKS_TIMEOUT_SECONDS5Timeout for JWT-authorizer JWKS fetches (HTTP API + REST API). Both proxy and JWKS fetches run off the event loop so a slow upstream cannot stall unrelated requests.
MINISTACK_IMDS_V2_REQUIRED0When 1, the IMDS service rejects token-less GET requests on /latest/meta-data/.... Callers must PUT /latest/api/token first and pass the value as X-aws-ec2-metadata-token, matching real-AWS hop-limit-1 IMDSv2-only instances. Point SDK callers via AWS_EC2_METADATA_SERVICE_ENDPOINT=http://localhost:4566.
MINISTACK_COGNITO_PRETOKEN_STRICT0When 1, a failing PreTokenGeneration Lambda fails the auth call the way real AWS does. Default is fail-open: a Lambda error is logged and the unmodified token is still issued, which keeps local-dev auth flows working when an unrelated Lambda is broken.
MINISTACK_CODEBUILD_EXECUTE0When 1, StartBuild runs the project's inline buildspec through the official AWS CodeBuild local agent in Docker (phases run, output streams to CloudWatch Logs) instead of returning a metadata-only SUCCEEDED record. Requires the Docker socket mounted into the container.
SFTP_ENABLED / SFTP_PORT(on) / 2222Transfer Family's real SFTP listener — on by default when asyncssh is installed (full image); SFTP_ENABLED=0 disables it, SFTP_PORT moves the shared listener, and SFTP_PORT_PER_SERVER=1 gives each server its own port.
CLOUDTRAIL_RECORDING0When 1, trails record an in-memory audit log served by LookupEvents (also toggleable at runtime via cloudtrail._recording_enabled).
MINISTACK_MSK_BOOTSTRAP (+ _TLS / _SASL_SCRAM / _SASL_IAM)(unset)External Kafka bootstrap strings returned by MSK GetBootstrapBrokers — MiniStack runs no broker; point these at one you bring.
APPSYNC_EVENTS_ENFORCE_AUTH0When 1, AppSync Events publish/subscribe strictly enforce the channel namespace's auth modes instead of the permissive default.
IOT_MTLS_ENABLED / IOT_MTLS_PORT(on) / 8883The IoT broker also accepts native MQTT over TLS on IOT_MTLS_PORT (default 8883), on by default when the cryptography package is installed. Set IOT_MTLS_ENABLED=0 to disable. The broker certificate is served at GET /_ministack/iot/ca.pem for devices to trust.
IOT_SESSION_EXPIRY_SECONDS3600Expiry for MQTT persistent sessions.
IOT_RETRANSMIT_SECONDS10Retransmit interval for unacknowledged QoS 1 messages.
IOT_WS_FRAME_MAX_BYTES16777216Maximum MQTT-over-WebSocket frame size.
ALB_TARGET_CONNECT_TIMEOUT_SECONDS / ALB_TARGET_IDLE_TIMEOUT_SECONDS10 / 60ALB data-plane connect and idle timeouts toward targets (also runtime-config keys alb.TARGET_CONNECT_TIMEOUT / alb.TARGET_IDLE_TIMEOUT).
CLOUDTRAIL_MAX_EVENTS10000Per-account ring-buffer cap for recorded CloudTrail events.
COGNITO_EMAIL_ENABLEDtrueSet false to skip sending Cognito verification / invite emails entirely.
COGNITO_DEFAULT_FROMno-reply@verificationemail.comFrom address for Cognito emails when the pool's EmailConfiguration.From is unset.
APPSYNC_EVENTS_HTTP_HOST_TEMPLATE / APPSYNC_EVENTS_REALTIME_HOST_TEMPLATE(AWS-shaped localhost vhosts)When both are set, they are str.format-ed with {api_id}, {region}, {port} to build the dns block returned on Event APIs.
APPSYNC_EVENTS_KA_INTERVAL_SECS60AppSync Events WebSocket keep-alive cadence (the spec's one ka per 60 s).
MINISTACK_BEDROCK_PROXY_URL(unset)Points Bedrock Runtime InvokeModel / Converse at any OpenAI-compatible backend; MINISTACK_BEDROCK_PROXY_TIMEOUT_SECONDS (default 30) bounds the call.
SFTP_HOST / SFTP_BASE_PORT0.0.0.0 / 2300Bind address for Transfer Family SFTP listeners, and the first port allocated when SFTP_PORT_PER_SERVER=1 gives each server its own port.

Admin endpoints

All admin endpoints live under /_ministack/*. Most are also exposed under /_localstack/* for LocalStack compatibility.

EndpointMethodPurpose
/_ministack/healthGETLiveness — returns 200 with a JSON map of service availability and init/ready script status.
/_ministack/readyGETReadiness — 200 when ready.d scripts have finished, 503 while still running.
/_ministack/resetPOSTWipes all in-memory service state. Add ?init=1 to re-run init.d scripts. Acquires a global reset lock so concurrent requests serialize.
/_ministack/configPOSTRuntime config for an exact-match whitelist of keys (e.g. athena.ATHENA_ENGINE, stepfunctions._SFN_WAIT_SCALE, lambda_svc.LAMBDA_EXECUTOR, cloudtrail._recording_enabled). Flat JSON body mapping each key to its value.
/_ministack/ses/messagesGETInspect sent emails. ?account=123456789012 filters by account.
/_ministack/lambda-code/{function_name}GETDownloads the function's deployment ZIP.
/_ministack/lambda-layers/{layer_name}/{version}/contentGETDownloads a layer version ZIP.
/_ministack/sqs/messagesGETInspect in-flight SQS messages. ?QueueUrl= restricts to one queue; ?account= filters by 12-digit account.
/_ministack/transfer/sftp-portsGETReturns the SFTP listener ports as {shared, per_server}.
/_ministack/iot/ca.pemGETThe IoT broker CA certificate, for devices to trust.
/_ministack/cfn-response/{token}PUTCallback target for CloudFormation custom resources — the ResponseURL their Lambda PUTs to (used internally, listed for completeness).

Init & ready scripts

On startup MiniStack runs executable files from (in order):

  • /docker-entrypoint-initaws.d/ (LocalStack-compat) or /etc/localstack/init/boot.d/ — ran synchronously before the server accepts traffic.
  • /docker-entrypoint-initaws.d/ready.d/ or /etc/localstack/init/ready.d/ — ran asynchronously after startup; /_ministack/ready flips to 200 once these finish.

Scripts see the usual AWS env (AWS_ACCESS_KEY_ID=test, AWS_SECRET_ACCESS_KEY=test, AWS_DEFAULT_REGION=us-east-1, AWS_ENDPOINT_URL=http://localhost:4566) pre-populated.

Runtime config

POST /_ministack/config mutates a limited set of keys without restarting. Example:

curl -X POST http://localhost:4566/_ministack/config \
  -H 'Content-Type: application/json' \
  -d '{"athena.ATHENA_ENGINE":"duckdb"}'

The whitelist is exact-match (not prefixes). Current keys: athena.ATHENA_ENGINE, athena.ATHENA_DATA_DIR, stepfunctions._sfn_mock_config, stepfunctions._SFN_WAIT_SCALE, lambda_svc.LAMBDA_EXECUTOR, cloudtrail._recording_enabled, alb.TARGET_CONNECT_TIMEOUT, alb.TARGET_IDLE_TIMEOUT. Tests use this to flip a single knob without tearing down the container.

Docker Compose

A ready-to-use setup with full persistence and Lambda Docker support:

services:
  ministack:
    image: ministackorg/ministack:latest
    ports:
      - "4566:4566"
    environment:
      - PERSIST_STATE=1
      - S3_PERSIST=1
      - RDS_PERSIST=1
      - LOG_LEVEL=INFO
      - LAMBDA_EXECUTOR=docker
      - DOCKER_NETWORK=myproject_default
    volumes:
      - ./data/state:/tmp/ministack-state
      - ./data/s3:/tmp/ministack-data/s3
      - /var/run/docker.sock:/var/run/docker.sock
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:4566/_ministack/health')"]
      interval: 10s
      timeout: 3s
      retries: 3
Tip: set DOCKER_NETWORK to your Compose project's network name (usually <folder>_default) so Lambda containers can reach MiniStack at http://ministack:4566 and container-backed services return routable endpoints.

Troubleshooting

Errors come back in the same format as AWS — XML for Query/XML services, JSON for JSON services. The ones you'll actually see:

ErrorMeaning
ResourceNotFoundExceptionThe resource (table, queue, function, etc.) doesn't exist. Usually means you're hitting MiniStack before your setup code runs.
ResourceAlreadyExistsExceptionYou're trying to create something that already exists. With PERSIST_STATE=1, this often means leftover state from a previous run — POST /_ministack/reset clears it.
ValidationExceptionInvalid parameters. Required fields and basic types are validated; optional fields more leniently than AWS.
UnknownOperationExceptionThe API action isn't implemented. The per-service pages list every supported operation.
UnsupportedResource (CloudFormation)The template uses a resource type without a provisioner. 135 types are supported — others fail the stack.
Undocumented knobs: services may read additional env vars for niche behaviors (e.g. SES template paths, CFN timeout overrides). When in doubt, grep -r "os.environ" ministack/ gives the definitive list.