overcast local AWS emulator

Reference

Full reference

Configuration variables, debug endpoints, persistence backends, multi-container networking, and startup troubleshooting — everything in one scannable page.

This directory contains the full Overcast documentation. For a quick overview, see the root README.

Contents

Getting started

Reference

Storage and performance

  • Performance — startup expectations, storage tuning, and where “feels slow” time actually goes
  • Storage backends — durability comparison and what survives a restart, per backend

Internal working plans (storage stabilization, storage access patterns, pagination fidelity, the storage regression test plan, and others) live in docs/plans/ in the repository, and contributor-facing developer docs (building from source, debugging, wire-protocol design, storage internals, AWS compatibility review tracking) live in docs/dev/ — both are deliberately excluded from this published documentation set. See CONTRIBUTING.md and AGENTS.md if you’re contributing to Overcast itself.


Support level legend

Every endpoint in the service docs carries one of these statuses:

StatusMeaning
✅ SupportedFully implemented. AWS SDK calls work as expected.
⚠️ PartialImplemented but with caveats. See the notes column for detail.
🚧 WIPUnder active development. May be broken or incomplete.
❌ UnsupportedNot implemented. Returns 501 Not Implemented.

Service emulation tiers

Each service also has an overall emulation tier, visible on the health endpoint (/_overcast/health) and the web dashboard:

TierMeaning
FullP1+P2 operations implemented. Real SDK clients can use it end-to-end.
PartialP1 operations implemented. Basic workflows work.
InertFull CRUD works — resources are created and stored — but no side-effects or enforcement occur. For example, IAM stores users, roles, and policies but never enforces permissions.
StubRegistered so discovery works: at most a hardcoded, stateless answer to the service’s describe call; every other operation returns 501 Not Implemented.

Endpoints marked Unsupported return a well-formed AWS error response so that SDKs surface a clear error rather than a connection failure:

HTTP 501 Not Implemented
x-emulator-unsupported: true

{
  "__type": "NotImplemented",
  "message": "This operation is not yet emulated. See https://github.com/overcast-sh/overcast/docs/services/<service>.md"
}

Services

For a shorter overview, start with the service reference index.

ServiceDocOpsCoverage tier
S3s3.md53Comprehensive / broad support
SQSsqs.md21Comprehensive / broad support
DynamoDBdynamodb.md28Comprehensive / broad support
Lambdalambda.md59Comprehensive / broad support
API Gatewayapigateway.md106Comprehensive / broad support
AppSyncappsync.md82Comprehensive / broad support
CloudFrontcloudfront.md89Comprehensive / broad support
Cognitocognito.md70Comprehensive / broad support
EC2 / VPCec2.md72Comprehensive / broad support
SNSsns.md30Comprehensive / broad support
IAMiam.md74Core CRUD + common workflows
ECSecs.md48Core CRUD + common workflows
ECRecr.md22Core CRUD + common workflows
KMSkms.md33Core CRUD + common workflows
Kinesiskinesis.md23Core CRUD + common workflows
EventBridgeeventbridge.md29Core CRUD + common workflows
Schedulerscheduler.md12Core CRUD + common workflows
CloudFormationcloudformation.md52Core CRUD + common workflows
RDSrds.md34Core CRUD + common workflows
ElastiCacheelasticache.md24Core CRUD + common workflows
EFSefs.md31Core CRUD + common workflows
AppConfigappconfig.md20Core CRUD + common workflows
AppConfigDataappconfigdata.md2Core CRUD + common workflows
Secrets Managersecretsmanager.md22Core CRUD + common workflows
SSMssm.md18Core CRUD + common workflows
CloudWatch Logscloudwatch-logs.md22Core CRUD + common workflows
SESses.md45Core CRUD + common workflows
STSsts.md11Core CRUD + common workflows
Route 53route53.md25Core CRUD + common workflows
Auto Scalingautoscaling.md25Core CRUD + common workflows
Step Functionsstepfunctions.md15Minimal / targeted support
Pipespipes.md8Minimal / targeted support
WAF v2waf.md7Minimal / targeted support
Shieldshield.md8Minimal / targeted support
ACMacm.md10Minimal / targeted support
Athenaathena.md11Minimal / targeted support
Bedrockbedrock.md2Minimal / targeted support
CloudWatchcloudwatch.md17Minimal / targeted support
DynamoDB Streamsdynamodbstreams.md4Minimal / targeted support
Firehosefirehose.md9Minimal / targeted support
Glueglue.md11Minimal / targeted support
OpenSearchopensearch.md8Minimal / targeted support
AppRegistryappregistry.md22IaC/discovery-oriented stub
Backupbackup.md12IaC/discovery-oriented stub
CloudTrailcloudtrail.md12IaC/discovery-oriented stub
EKSeks.md50IaC/discovery-oriented stub
ELBv2elb.md21IaC/discovery-oriented stub
MSKmsk.md30IaC/discovery-oriented stub
Organizationsorganizations.md9IaC/discovery-oriented stub
Transfer Familytransfer.md13IaC/discovery-oriented stub

Want to add support for a new AWS service? See CONTRIBUTING.md § How to add a service in the repository.


Configuration reference

All configuration is via environment variables. No config file required.

The web console’s OVERCAST_UI_PORT is documented under Web management console; everything the Go emulator itself reads is below.

VariableDefaultDescription
OVERCAST_LISTEN0.0.0.0 containerised, 127.0.0.1 native (see #761)Hostname or IP to bind the AWS API to (LocalStack’s GATEWAY_LISTEN idiom — not the same thing as OVERCAST_HOSTNAME below). Accepts a comma-separated list to bind several, e.g. 127.0.0.1,172.17.0.1 to be reachable from this machine and from its containers over the Docker bridge without being on any network the machine is attached to. A wildcard cannot be combined with a specific address. The web console binds the first address only. An explicit value always wins over the default, in either direction (e.g. OVERCAST_LISTEN=0.0.0.0 restores the old native reach from a VM or another machine). Renamed from OVERCAST_HOST, which has been removed: a leftover OVERCAST_HOST fails at startup naming this variable instead of being silently ignored
OVERCAST_HOSTNAMElocalhostHostname embedded in client-facing URLs (SQS queue URLs, Lambda function URLs, API Gateway apiEndpoint, AppSync DNS names, CloudFront domain names). Set it to localhost.overcast.sh unless you are offline: every *.localhost.overcast.sh name resolves to 127.0.0.1 on every OS, which plain localhost does not on Windows. See networking.md. LocalStack’s LOCALSTACK_HOST is accepted as a compatibility alias (see the row below)
LOCALSTACK_HOST (alias)(none)LocalStack-compatibility alias for OVERCAST_HOSTNAME (#1190) — Overcast is meant to be a drop-in replacement for LocalStack, so its documented settings are honoured directly rather than requiring a rename. Accepts LocalStack’s hostname[:port] format (e.g. localhost.localstack.cloud:4566): the hostname part maps to OVERCAST_HOSTNAME, and a port part is accepted only if it matches OVERCAST_PORT. Setting both OVERCAST_HOSTNAME and LOCALSTACK_HOST to the same hostname is fine (the natural result of migrating a compose file line by line); setting them to different hostnames, or a LOCALSTACK_HOST port that disagrees with OVERCAST_PORT, fails startup naming both rather than silently preferring one. A startup log line names the alias whenever it was recognised
HOSTNAME_EXTERNAL (alias)(none)Legacy LocalStack name LOCALSTACK_HOST replaced; also accepted as a compatibility alias for OVERCAST_HOSTNAME (#1190), chained after LOCALSTACK_HOST — all three spellings must agree when more than one is set. Never carried a port suffix, unlike LOCALSTACK_HOST
OVERCAST_SPLIT_HORIZON_HOSTS(none)Extra comma-separated hostnames remapped to Overcast inside containers it starts (ECS tasks), so one URL is dialable from both host and container. Added to the built-in localhost.overcast.sh, localhost.localstack.cloud, localhost.floci.io
OVERCAST_PORT4566TCP port. LocalStack’s EDGE_PORT is accepted as a direct compatibility alias (#1190)
EDGE_PORT (alias)(none)LocalStack-compatibility alias for OVERCAST_PORT (#1190) — same format, direct pass-through. Disagreeing with an explicit OVERCAST_PORT fails startup naming both
GATEWAY_LISTEN (alias)(none)LocalStack-compatibility alias for OVERCAST_LISTEN and OVERCAST_PORT together (#1190). Accepts LocalStack’s <ip>:<port>[,<ip>:<port>...] format: the address(es) map to OVERCAST_LISTEN, the port to OVERCAST_PORT. Every entry must share the same port — a GATEWAY_LISTEN naming more than one port has no single OVERCAST_PORT to map to and is a documented non-match (fails startup rather than picking one and dropping the other bind). Counts as an explicit bind-address setting, so it overrides the environment-dependent OVERCAST_LISTEN default the same way an explicit OVERCAST_LISTEN would
OVERCAST_STATEautoStorage backend: auto (default when unset), memory, hybrid, persistent, or wal. auto resolves to hybrid when a volume/bind mount or existing database is found at OVERCAST_DATA_DIR (or the dir was explicitly set), otherwise memory — see storage.md § The auto default. In the overcast-slim image and the overcastd binaries, hybrid/persistent are not compiled in and auto is always memory; use wal — see storage.md § Builds without SQLite. LocalStack’s PERSISTENCE=1 is accepted as a compatibility alias for persistent (#1190); PERSISTENCE=0 is a no-op, leaving auto’s own detection in place
OVERCAST_STATE_<SERVICE>(global)Per-service backend override, e.g. OVERCAST_STATE_S3=memory
OVERCAST_HYBRID_FLUSH_INTERVAL5sHow often the hybrid backend flushes in-memory state to disk
OVERCAST_HYBRID_SYNCintervalHybrid pending-log fsync policy: always, interval, or never
OVERCAST_HYBRID_SYNC_INTERVAL100msPeriodic fsync interval used when OVERCAST_HYBRID_SYNC=interval
OVERCAST_HYBRID_DIRTY_ENTRY_THRESHOLD10000Unflushed-write count that triggers an early hybrid flush ahead of the timer (<= 0 disables)
OVERCAST_HYBRID_DIRTY_BYTE_THRESHOLD8388608Approximate unflushed-write bytes that trigger an early hybrid flush (default 8 MiB; <= 0 disables)
OVERCAST_HYBRID_MAINTENANCE_INTERVAL5mHow often the hybrid backend runs background SQLite housekeeping (passive WAL checkpoint + conditional incremental vacuum)
OVERCAST_WAL_FSYNCintervalWAL fsync policy: always, interval, or never
OVERCAST_WAL_FSYNC_INTERVAL100msPeriodic fsync interval used when OVERCAST_WAL_FSYNC=interval
OVERCAST_WAL_MAX_LOG_BYTES67108864WAL log compaction threshold in bytes (default 64 MiB)
OVERCAST_DATA_DIR~/.overcast/dataDirectory for store files and other on-disk state. LocalStack’s DATA_DIR is accepted as a direct compatibility alias (#1190) — setting it counts as an explicitly configured data directory for OVERCAST_STATE=auto’s detection, the same as OVERCAST_DATA_DIR itself would
OVERCAST_CA_DIR$OVERCAST_DATA_DIR/caWhere the local overcast CA lives. Separable from the data dir because the two have opposite lifetimes: state is disposable, a CA is a trust anchor you installed into this machine once. Point it at a host-owned directory (-v ~/.overcast/data/ca:/ca:ro) so an ephemeral container mints leaves from a root that outlives it — see HTTPS and HTTP/2. May be read-only
OVERCAST_DEFAULT_REGIONus-east-1Fallback region used in ARNs when not present in SigV4 header. LocalStack’s DEFAULT_REGION is accepted as a direct compatibility alias (#1190)
OVERCAST_ACCOUNT_ID000000000000Account ID embedded in ARNs
OVERCAST_LOG_LEVELinfotrace, debug, info, warn, error — see Log levels below. LocalStack’s DEBUG=1 is accepted as a compatibility alias for debug (#1190); DEBUG=0 is a no-op
OVERCAST_DEBUGfalseEnable /_overcast/debug/* endpoints
OVERCAST_DEBUG_TRACE_BUFFER1000User-facing request traces always retained — the floor. Only read when OVERCAST_DEBUG=true
OVERCAST_DEBUG_TRACE_CEILING10000How far a burst may grow retention past the floor
OVERCAST_DEBUG_TRACE_WINDOW1hHow long traces above the floor survive before being reclaimed
OVERCAST_DEBUG_TRACE_PINNED1000Traces kept because they went wrong, exempt from the floor and the window
OVERCAST_DEBUG_TRACE_BYTES_MB512Retained request/response body budget. Reclaims ordinary overflow first, then the oldest kept failures; never below the floor
OVERCAST_SIGV4_VALIDATEfalseVerify SigV4 signatures (header-signed and presigned URLs) and reject invalid or expired ones with 403 InvalidSignatureException. Signing secrets resolve through IAM user access keys and STS session credentials, falling back to the local-dev default test. Unsigned requests still pass through
OVERCAST_ENFORCE_IAMfalseEvaluate the calling principal’s IAM policies before each request and return AWS-shaped AccessDenied when they do not allow it. Off by default; with it off nothing is evaluated and no policy is read. See iam.md § Request-time enforcement
OVERCAST_ENFORCE_APIGATEWAY_THROTTLEfalseReject API Gateway requests that exceed their usage plan’s throttle or quota with AWS’s 429. Off by default: the limits are measured and reported (GetUsage, apigateway:Throttled events) but never rejected — see API Gateway
OVERCAST_CFN_SYNC_WAIT_MS1000Milliseconds CloudFormation waits for fast stack provisioning before returning (0 disables)
OVERCAST_STEPFUNCTIONS_EXECUTION_TIMEOUT15mRunaway guard on one Step Functions execution. Executions run off the request path, so this never bounds StartExecution itself; a state machine’s own TimeoutSeconds can lower it but not raise it. Exceeding it ends the execution TIMED_OUT with States.Timeout
OVERCAST_TLSauto = serve API and web UI over HTTPS with a certificate minted from the local overcast CA (unlocks browser HTTP/2) — see HTTPS and HTTP/2
OVERCAST_TLS_CERTPath to your own TLS certificate (enables HTTPS for API and web UI; mutually exclusive with OVERCAST_TLS=auto)
OVERCAST_TLS_KEYPath to the matching TLS private key
OVERCAST_SHUTDOWN_TIMEOUT5sGraceful shutdown wait; also budgets the final store flush — if it can’t finish in time the process exits anyway and unflushed writes replay from the pending log on next start
OVERCAST_PROTOCOL_STRICTfalseReturn 415 when a request arrives in a protocol the target service does not declare, instead of attempting the decode anyway
OVERCAST_DNStrueRun the built-in DNS resolver that serves the split-horizon names to the containers Overcast starts. Failing to bind the port is not fatal
OVERCAST_DNS_PORT53Port for the built-in DNS resolver. Docker’s --dns cannot express a port, so anything other than 53 is only useful for tests
OVERCAST_HOT_RELOADfalseUmbrella switch for hot reload across every compute service — see The inner loop
OVERCAST_LAMBDA_HOT_RELOAD(OVERCAST_HOT_RELOAD)Per-service override: hot reload for Lambda functions
OVERCAST_ECS_HOT_RELOAD(OVERCAST_HOT_RELOAD)Per-service override: hot reload for ECS tasks
OVERCAST_EC2_VPC_STRATEGYsharedHow VPCs map to Docker networks: shared, strict, or remapped (strict and remapped currently fall back to shared with a startup warning; netns is rejected) — see Local VPCs
OVERCAST_MCP_REMOTE_EXPOSUREfalseSecurity-relevant. Declares that the MCP endpoint (/_overcast/mcp) will be reachable by non-local clients, and turns on bearer-token auth for every MCP request. Setting it true makes OVERCAST_MCP_AUTH_TOKEN mandatory — Overcast refuses to start without one. Note it does not itself change what Overcast binds: if OVERCAST_LISTEN exposes the port, the MCP endpoint is exposed with it, so set this (and a token) before exposing the port beyond localhost. Browser Origin checks (localhost origins only) are enforced on MCP regardless
OVERCAST_MCP_AUTH_TOKENBearer token every MCP request must present once set (mandatory when OVERCAST_MCP_REMOTE_EXPOSURE=true; setting it alone also enables the auth check). Treat it like any other credential — anyone holding it can drive the emulator through MCP
OVERCAST_NETWORKovercastDocker network every container Overcast starts is reachable on by name when it belongs to no VPC — the default data plane. A resource that names a VPC joins that VPC’s network instead. Overcast derives a second network from this, <name>_control, which carries the Lambda Runtime API and the emulator endpoint; see container networking
LAMBDA_DOCKER_SOCKET/var/run/docker.sockDocker endpoint — Unix path or tcp://host:port (for DinD). The per-service socket overrides below must all address the same daemon: containers are attached to shared networks across service boundaries
LAMBDA_RUNTIME_API_PORT9001Port Overcast exposes the Lambda Runtime API on. The addresses are not configurable and do not follow OVERCAST_LISTEN: Overcast binds loopback plus the one address containers on the control plane reach it at — its own address on that network when Overcast is containerised, the network’s gateway on a native Linux daemon, the host’s routable address on Docker Desktop
LAMBDA_DOCKER_MAX_CONCURRENT_STARTS(auto)Max concurrent Docker-backed Lambda container starts. Unset: derived from the Docker host as clamp(NCPU/2, 2, 8) (each start bursts ~2 CPUs during INIT); 4 when Docker /info is unavailable
LAMBDA_MAX_INSTANCES(auto)Max Lambda containers across all functions. Unset: derived from the Docker host as clamp(MemTotal×0.65 / 256 MiB, 4, 32); 25 when /info is unavailable
LAMBDA_MAX_INSTANCES_PER_FUNCTION(auto)Max concurrent containers for one function. Unset: clamp(maxInstances/2, 2, maxInstances); 10 when /info is unavailable
LAMBDA_MAX_MEMORY_MB(auto)Aggregate memory budget for live Lambda containers (Σ MemorySize, in MB). Unset: 65% of the Docker host’s MemTotal; unlimited when /info is unavailable
LAMBDA_MAX_WARM_INSTANCES10Idle containers kept warm per function after a burst
LAMBDA_SEED_RUNTIME_IMAGESfalsePre-pull every currently-supported Lambda runtime image at startup
LAMBDA_INIT_TIMEOUT_SECONDS10Max seconds to wait for a Lambda runtime to finish INIT. LocalStack’s LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT is accepted as a direct compatibility alias (#1190)
LAMBDA_KEEP_CONTAINERSfalseKeep stopped Lambda containers after expiry/delete (useful for debugging)
LAMBDA_TAR_CACHE_MB256In-memory cache of pre-built cold-start code and layer tars; 0 disables it
LAMBDA_PROACTIVE_INITtruePre-initialize one execution environment once a function’s configuration settles; set false to opt out
LAMBDA_FETCH_REMOTE_LAYERSfalseDownload layers missing locally from real AWS (needs the LAMBDA_REMOTE_AWS_* credentials)
LAMBDA_LAYER_CACHE_DIR$OVERCAST_DATA_DIR/layersWhere layer zips are looked up and cached, named {sha256(arn)}.zip
LAMBDA_REMOTE_AWS_ACCESS_KEY_IDAWS access key ID used by LAMBDA_FETCH_REMOTE_LAYERS
LAMBDA_REMOTE_AWS_SECRET_ACCESS_KEYAWS secret access key used by LAMBDA_FETCH_REMOTE_LAYERS
LAMBDA_REMOTE_AWS_SESSION_TOKENOptional AWS session token used by LAMBDA_FETCH_REMOTE_LAYERS
ECS_DOCKER_SOCKET(Lambda socket)Docker endpoint for ECS — Unix path or tcp://host:port
ECS_KEEP_CONTAINERSfalseKeep stopped ECS task containers after they exit
OVERCAST_RDS_MODElivelive runs a real engine container per instance; mock is metadata-only
RDS_DOCKER_SOCKET(Lambda socket)Docker endpoint for RDS — Unix path or tcp://host:port
RDS_PORT_BASE33060Starting host port for RDS containers (each instance gets the next available port)
RDS_KEEP_CONTAINERSfalseKeep stopped RDS containers after instance deletion
ELASTICACHE_DOCKER_SOCKET(Lambda socket)Docker endpoint for ElastiCache — Unix path or tcp://host:port
ELASTICACHE_PORT_BASE63790Starting host port for ElastiCache engine containers
ELASTICACHE_KEEP_CONTAINERSfalseKeep stopped ElastiCache containers after deletion
MSK_DOCKER_SOCKET(Lambda socket)Docker endpoint for MSK — Unix path or tcp://host:port
MSK_PORT_BASE49092Starting host port for MSK broker containers
MSK_KEEP_CONTAINERSfalseKeep stopped MSK containers after cluster deletion
OVERCAST_EKS_MODEmockmock is metadata-only; live runs real cluster containers — see eks.md
EKS_DOCKER_SOCKET(Lambda socket)Docker endpoint for EKS — Unix path or tcp://host:port
OVERCAST_EFS_MODElivelive backs file systems with real storage (inert without Docker); mock is metadata-only — see efs.md
EFS_DOCKER_SOCKET(Lambda socket)Docker endpoint for EFS — Unix path or tcp://host:port
OVERCAST_EFS_NFSfalseRun one NFS-Ganesha export container per mount target (live mode only) — see efs.md
EFS_NFS_PORT_BASE22049Starting host port for the NFS export containers
EFS_NFS_IMAGEregistry.k8s.io/sig-storage/nfs-provisioner@sha256:…Digest-pinned image used for the NFS export containers
OVERCAST_ECR_REGISTRY_PORT4510Host port the shared ECR registry container asks for; 0, or a port already taken, falls back to an ephemeral port
OVERCAST_ECR_REGISTRY_PERSISTtrueBack the fixed-port registry with a named Docker volume, so pushed images survive a restart
OVERCAST_SMTP_MOCKtrueEnable built-in SMTP capture server (auto-disabled when OVERCAST_SMTP_HOST is set)
OVERCAST_SMTP_PORT1025Port for the mock SMTP server
OVERCAST_SMTP_HOSTExternal SMTP relay hostname (disables the mock server)
OVERCAST_SMTP_FROMovercast@localhostEnvelope From address for outbound SNS email notifications
OVERCAST_SMTP_USERNAMESMTP AUTH PLAIN username for external relay
OVERCAST_SMTP_PASSWORDSMTP AUTH PLAIN password for external relay
OVERCAST_SMTP_TLSfalseEnable implicit TLS (port 465) for external relay
OVERCAST_SMTP_INBOX_MAX500Maximum number of captured messages retained before eviction
OVERCAST_INIT_ENABLEDtrueRun init-hook scripts found in OVERCAST_INIT_DIRS at startup; set false to disable
OVERCAST_INIT_DIRS/etc/localstack/init,/etc/overcast/initComma-separated base directories scanned for init-hook scripts in stage subdirs (boot.d/, start.d/, ready.d/, shutdown.d/); LocalStack’s layout is honoured for drop-in migration — see Migrating from LocalStack
OVERCAST_INIT_TIMEOUT30sPer-script timeout for init hooks
SERVICES (ignored)(none)LocalStack variable recognised but with no effect (#1190) — Overcast runs every service, always, so there is nothing to select. Not rejected; a startup log line names it as seen
LOCALSTACK_API_KEY (ignored)(none)LocalStack variable recognised but with no effect (#1190) — Overcast has no LocalStack Pro/auth-gated feature set to unlock. Not rejected; a startup log line names it as seen
LOCALSTACK_AUTH_TOKEN (ignored)(none)Same as LOCALSTACK_API_KEY above (#1190)

Service names

Every service listed below always runs — there is no way to switch one off, and nothing to configure to get one. The names matter for one thing: they are what the per-service storage override OVERCAST_STATE_<SERVICE> is keyed by, in upper case. CloudWatch Logs is logs, so its override is OVERCAST_STATE_LOGS — not OVERCAST_STATE_CLOUDWATCH_LOGS, which names nothing and is rejected at startup.

Each name is the service’s AWS CLI name, which for several services matches neither the display name nor the aws-cdk-lib module you would import. The CDK column is there because that is the mapping people most often need to make.

For per-service endpoint coverage, follow the doc links in Services above.

NameServiceCDK module (aws-cdk-lib/…)
s3S3aws-s3
sqsSQSaws-sqs
dynamodbDynamoDBaws-dynamodb
lambdaLambdaaws-lambda
apigatewayAPI Gatewayaws-apigateway, aws-apigatewayv2
appsyncAppSyncaws-appsync
cloudfrontCloudFrontaws-cloudfront, aws-cloudfront-origins
cognitoCognitoaws-cognito
ec2EC2 / VPCaws-ec2
snsSNSaws-sns
iamIAMaws-iam
ecsECSaws-ecs
ecrECRaws-ecr, aws-ecr-assets
kmsKMSaws-kms
kinesisKinesisaws-kinesis
eventbridgeEventBridgeaws-events, aws-events-targets
schedulerScheduleraws-scheduler
cloudformationCloudFormationaws-cloudformation
rdsRDSaws-rds
elasticacheElastiCacheaws-elasticache
efsEFSaws-efs
appconfigAppConfigaws-appconfig
appconfigdataAppConfigData— (runtime data plane; no constructs)
secretsmanagerSecrets Manageraws-secretsmanager
ssmSSMaws-ssm
logsCloudWatch Logsaws-logs
sesSESaws-ses
stsSTS— (used by the CDK CLI itself)
route53Route 53aws-route53, aws-route53-targets
autoscalingAuto Scalingaws-autoscaling, aws-applicationautoscaling
stepfunctionsStep Functionsaws-stepfunctions, aws-stepfunctions-tasks
pipesPipesaws-pipes
wafWAF v2aws-wafv2
shieldShieldaws-shield
acmACMaws-certificatemanager
athenaAthenaaws-athena
bedrockBedrockaws-bedrock
cloudwatchCloudWatchaws-cloudwatch, aws-cloudwatch-actions
dynamodbstreamsDynamoDB Streams— (enabled by the stream prop on aws-dynamodb)
firehoseFirehoseaws-kinesisfirehose
glueGlueaws-glue
opensearchOpenSearchaws-opensearchservice
appregistryAppRegistryaws-servicecatalogappregistry
backupBackupaws-backup
cloudtrailCloudTrailaws-cloudtrail
eksEKSaws-eks
elbv2ELBv2aws-elasticloadbalancingv2
mskMSKaws-msk
organizationsOrganizations— (no constructs)
transferTransfer Familyaws-transfer

Log levels

OVERCAST_LOG_LEVEL controls how much Overcast logs, from quietest to noisiest:

LevelWhat you’ll see
infoDefault. Lifecycle events (start, shutdown, migrations) and one line per AWS API call your app makes.
debugEverything in info, plus the reasoning behind each response — what to attach to a bug report.
traceEverything in debug, plus emulator machinery: health-check probes, web UI polling, background flush/sweep ticks. Very high volume — use for a short capture window, not always-on.
warnOne-liners for handled-but-unexpected conditions (a malformed record was skipped, a slow filesystem was detected).
errorOne-liners for failures that need attention (storage degraded, a migration failed).

For contributors: the full call-site policy (what belongs at debug vs trace) is documented in CONTRIBUTING.md § Log levels.


Persistence

Overcast supports four concrete storage backends, set via OVERCAST_STATE:

BackendDescription
autoDefault when unset. Resolves to hybrid or memory at startup — see below.
memoryAll state in-process; lost on restart. Fastest — ideal for CI.
hybridReads from memory, flushes to SQLite asynchronously. Fast with durability.
persistentEvery mutation written synchronously to SQLite. Fully durable, slightly slower.
walIn-memory reads + append-log durability with replay on startup and periodic compaction.

OVERCAST_STATE is unset by default, which means auto: Overcast picks a mode based on whether a durable data location was provided — a volume or bind mount at the data directory resolves to hybrid (persist); nothing mounted resolves to memory. In CI, where containers typically run with no data volume, this means auto lands on memory — the fast, ephemeral mode CI wants — with zero configuration. See storage.md § The auto default for the full decision rule (it also covers native, non-Docker runs).

For state that persists across restarts, just mount a volume — auto does the rest:

docker run --rm \
  -p 4566:4566 \
  -v $(pwd)/overcast-data:/data \
  ghcr.io/overcast-sh/overcast:alpha

This resolves to hybrid automatically because a volume is mounted at /data. Set OVERCAST_STATE explicitly (e.g. -e OVERCAST_STATE=persistent) if you need a different backend than what auto would pick.

[!IMPORTANT] The overcast-slim image and the overcastd binaries are built without SQLite, so hybrid and persistent do not exist in them: auto always resolves to memory there and the mounted volume above would be ignored — state is lost on every restart, with no error. Add -e OVERCAST_STATE=wal (the one durable backend those artifacts do have), or use the full ghcr.io/overcast-sh/overcast image. See storage.md § Builds without SQLite.

Persistent/hybrid SQLite data lives at $OVERCAST_DATA_DIR/overcast.db. WAL mode uses $OVERCAST_DATA_DIR/overcast.wal. You can also override the backend per-service:

Hybrid seeds small control-plane namespaces into memory on startup and reads large data-plane namespaces (messages, log events, metric datapoints) from SQLite on every access — there is no read-through cache for those, by design — so background schedulers and dashboards do not continuously poll SQLite for hot resource metadata, while high-volume data never has to fit in memory. See storage.md for the full backend comparison, or dev/storage-backends.md for the implementation internals.

-e OVERCAST_STATE=memory -e OVERCAST_STATE_S3=hybrid

Per-service storage overrides

Each service can use a different backend. Set OVERCAST_STATE_<SERVICE> where <SERVICE> is one of the service names in upper case, so CloudWatch Logs is OVERCAST_STATE_LOGS:

docker run --rm -p 4566:4566 \
  -e OVERCAST_STATE=memory \
  -e OVERCAST_STATE_DYNAMODB=persistent \
  -e OVERCAST_STATE_S3=hybrid \
  -v $(pwd)/data:/data \
  ghcr.io/overcast-sh/overcast:alpha

Note: a few services accept an override that can have no effect, and log a startup warning when one is set: DYNAMODBSTREAMS (a facade over the dynamodb service, which owns all stream state), STS (its session state lives under IAM’s storage), and BEDROCK/ORGANIZATIONS (stateless stubs). Every other service’s override works.

In this example DynamoDB writes synchronously to disk, S3 flushes asynchronously, and every other service uses in-memory (ephemeral) storage. Each overridden service gets its own SQLite file under $OVERCAST_DATA_DIR/<service>/.

The active storage configuration is visible in three places:

  • GET /_overcast/health — the storage object shows the resolved default backend (default), what was actually configured (configured — e.g. auto, when default was resolved rather than explicitly set), per-service overrides, and persistent backend health including pending hybrid writes when available.
  • Dashboard footer — the web management console displays the storage mode with a tooltip listing overrides.
  • Startup log — when OVERCAST_STATE resolves via auto, Overcast logs which mode it picked and why (e.g. storage mode auto-detected: memory (no persistence signal found...) — set OVERCAST_STATE to override). The web console’s Metrics & Health page also surfaces this as an advisory whenever the resolved mode is memory.

HTTPS / TLS

Full guide: HTTPS and HTTP/2 — why the web console needs it (browsers cap HTTP/1.1 at 6 connections per origin, localhost included, and never speak cleartext HTTP/2, so the console’s SSE + progress streams starve navigation under load), the trust model, offline behaviour, and the manual setup path.

The two-command version:

overcast https enable            # once per machine: local CA → system trust store
OVERCAST_TLS=auto overcast serve # both listeners now serve HTTPS + HTTP/2

Running in Docker? Still those two commands — mount the CA the first one created, read-only, so the container mints certificates from a root this machine already trusts and recreating it never costs you another approval prompt:

overcast https enable            # once per machine
docker run -d -e OVERCAST_TLS=auto \
  -e OVERCAST_CA_DIR=/ca -v ~/.overcast/data/ca:/ca:ro \
  -p 4566:4566 -p 4567:4567 ghcr.io/overcast-sh/overcast:alpha

No overcast on the host? The daemon can mint its own CA and serve the certificate at /_overcast/ca.pem for overcast https enable --endpoint https://localhost:4566 to install — keep OVERCAST_CA_DIR on a named volume so it survives recreation. See HTTPS and HTTP/2.

Then open https://localhost.overcast.sh:4567 (public DNS resolves *.localhost.overcast.sh to 127.0.0.1 — no hosts-file edits). Both the API (4566) and the web UI (4567) are served over TLS; browsers negotiate HTTP/2 via ALPN and multiplex everything over one connection.

Prefer your own certificate? OVERCAST_TLS_CERT/OVERCAST_TLS_KEY still work and now also apply to the web UI:

docker run --rm \
  -p 4566:4566 -p 4567:4567 \
  -e OVERCAST_TLS_CERT=/certs/cert.pem \
  -e OVERCAST_TLS_KEY=/certs/key.pem \
  -v $(pwd):/certs \
  ghcr.io/overcast-sh/overcast:alpha
export AWS_CA_BUNDLE=~/.overcast/data/ca/rootCA.pem  # AWS CLI + boto3 (auto mode)
export NODE_EXTRA_CA_CERTS=~/.overcast/data/ca/rootCA.pem # Node.js SDK

Multi-container networking

When running Overcast inside Docker Compose alongside application containers, client-facing URLs (e.g. SQS queue URLs, SNS unsubscribe links, RDS endpoints) default to localhost — which won’t resolve from a sibling container.

Set OVERCAST_HOSTNAME to the Docker Compose service name so returned URLs are reachable across the network:

services:
  overcast:
    image: ghcr.io/overcast-sh/overcast:alpha
    environment:
      OVERCAST_HOSTNAME: overcast # SQS QueueUrl → http://overcast:4566/...
    ports:
      - "4566:4566"

  app:
    build: .
    environment:
      AWS_ENDPOINT_URL: http://overcast:4566
    depends_on:
      - overcast

Debug endpoints

Set OVERCAST_DEBUG=true to enable the /_overcast/debug namespace and request tracing. Every response carries a request ID (x-amzn-requestid for most services, x-amz-request-id for S3) which can be used to look up the full trace:

EndpointMethodDescription
/_overcast/healthGETBasic health check (always enabled)
/_overcast/eventsGETSSE stream of internal events (always enabled)
/_overcast/metricsGETGo runtime memory/GC/goroutine stats (always enabled)
/_overcast/topologyGETFull cross-region resource graph (always enabled)
/_overcast/preflight/regionGETWhether resources of a ?kind= exist in some region other than the caller’s, and how many (always enabled). Answers with nothing to report when the caller’s own region has any — it explains an empty list, it is not a census
/_overcast/debug/healthGETDetailed: uptime, services, state backend and health
/_overcast/debug/configGETEffective configuration (secrets redacted)
/_overcast/debug/stateGETEvery namespace and its keys (no values)
/_overcast/debug/state/{namespace}GETPaginated key/value pages for one namespace (?after= cursor, ?limit= ≤ 5000, default 500); ?key= fetches one raw value
/_overcast/debug/resetPOSTWipe all state
/_overcast/debug/reset/{service}POSTWipe state for one service
/_overcast/debug/metricsGETStorage diagnostics: flush history, seed duration, pending-log size; ?includeRowCounts=true adds per-namespace row counts
/_overcast/debug/pprof/GETGo pprof index (goroutine, heap, CPU profiles, etc.)
/_overcast/debug/trace/{requestId}GETFull trace for one request: bodies, headers, log entries, AWS errors
/_overcast/debug/tracesGETPaginated list of recent traces; filterable by ?service=, ?method=, ?path=, ?status=, ?search=
/_overcast/debug/traces/countGETCurrent trace buffer count and capacity
/_overcast/debug/traces/searchGETFree-text search over retained traces
/_overcast/debug/ec2/vpcsGETEC2 VPC-to-Docker-network wiring, for debugging VPC-backed networking. Service-specific debug routes live under /_overcast/debug/<service>/…; this is the only one today

Traces are retained under three rules, so that the request explaining a failure is still there when you go looking, without your having configured anything first:

  1. The newest OVERCAST_DEBUG_TRACE_BUFFER traces (default 1000) are always kept.
  2. Beyond that, a burst is kept for OVERCAST_DEBUG_TRACE_WINDOW (default 1h), up to OVERCAST_DEBUG_TRACE_CEILING (default 10000). A cdk deploy pushes thousands of requests through in a couple of minutes, and the floor alone would keep the rollback traffic and discard the error that started it.
  3. Traces that went wrong — a 4xx/5xx, an AWS error code, or a failed internal hop — are exempt from both, up to OVERCAST_DEBUG_TRACE_PINNED (default 1000). They are not exempt from the memory budget: under real pressure the oldest kept failures are surrendered last, after every ordinary trace above the floor.

Internal polling (health checks, the console’s own requests) is retained separately and can never evict a request you made. A trace records each internal service-to-service hop a request made, and captures a goroutine stack for the first 20 hops plus the first 20 hops that failed — a CloudFormation or CDK deploy dispatches hundreds of hops through one trace, and a stack for every one of them would cost more than it tells you. Hops past that budget show “Stack trace not captured” in the console.


Event pipelines

PipelineStatus
SNS → SQS subscription✅ Supported
SQS → Lambda event source mapping✅ Supported
DynamoDB Streams → SQS (Pipes)✅ Supported
DynamoDB Streams → Lambda (ESM)✅ Supported

Web management console

The full image (ghcr.io/overcast-sh/overcast) includes a web management console accessible at http://localhost:4567 (configurable via OVERCAST_UI_PORT env var / --ui-port flag; 0 disables it).

The console provides:

  • Dashboard with service cards and real-time status
  • Service-specific UI for all implemented services (S3 browser, SQS message inspector, DynamoDB item editor, Lambda test/invoke, etc.)
  • Live activity feed — a real-time stream of API calls as they happen across all services, showing the operation, resource, status code, and latency. Useful for understanding what your application is actually doing against the emulated APIs.
  • Inbox — a built-in capture inbox for all outbound email and SMS messages generated by SES, SNS, and Cognito. Instead of messages disappearing into the void (or requiring a real SMTP server), the Inbox collects them and lets you browse, search, and inspect each message’s headers and body. This makes it easy to verify that your application sends the right emails during local development and testing — no third-party mail catcher needed.
  • Topology map showing cross-service relationships
  • Real-time updates via SSE

The web UI is non-critical — the AWS API on port 4566 does not depend on it. It is served in-process by the same binary (there is no separate console server to start or supervise), and OVERCAST_UI_PORT=0 / --ui-port 0 turns it off entirely.

[!TIP] If the console feels sluggish or stops responding to clicks while many Lambdas run or transfers are in flight, you are hitting the browser’s 6-connection HTTP/1.1 limit — the live feed and progress streams are holding the sockets. Serve the console over HTTPS to unlock HTTP/2 and keep it responsive under any load: see HTTPS and HTTP/2.


Troubleshooting

Startup preflight

A handful of environment mistakes cost real time on this project because they don’t look like environment mistakes — Overcast answers normally, and the symptom (an empty console list, a container that never starts, data that isn’t where you left it) reads exactly like a bug in the emulator. Where Overcast can tell, it says so: one actionable WARN, the moment the symptom appears, never a wall of startup output and never on a healthy setup.

Message names…Means
No stacks in <region>. There are N in <region>.The selected region has nothing, but another region does — check AWS_REGION/AWS_DEFAULT_REGION against what you expect. Served on demand at /_overcast/preflight/region, not logged at startup.
Docker is not reachable for: ...One or more container-backed services (ECS, RDS, Lambda, MSK, etc.) couldn’t reach a Docker daemon and will run metadata-only — container creates fail instead of starting anything. Start Docker, or check the socket is readable by this user.
the API is published on a different host port than it listens onThe container remapped its port (e.g. -p 4580:4566). Overcast already rewrites the common case (queue URLs, split-horizon hostnames); publish 1:1 instead if something still compares the port literally (a Cognito token’s iss).
a request arrived addressed to "..." — a real AWS hostnameA hosts-file entry, DNS override, or proxy is sending *.amazonaws.com-bound traffic to Overcast. Point AWS_ENDPOINT_URL at Overcast explicitly, or remove the redirect if that wasn’t intentional.
OVERCAST_HOSTNAME=... does not resolve / virtual-hosted-style addressing will not workThis host’s resolver can’t resolve OVERCAST_HOSTNAME (or its subdomains) — breaks virtual-hosted S3 and cdk deploy asset publishing. The message names the fix for this host.
this run is memory-only, but an existing Overcast database was foundOVERCAST_STATE=memory (explicitly, or a -tags nosqlite build resolving auto to memory regardless) is ignoring a database that already has data in it. Set OVERCAST_STATE=auto (or hybrid/wal) to use it instead.
Running in memory-only mode (auto-detected)No volume is mounted and no OVERCAST_DATA_DIR is set — state won’t survive a restart. Expected outside of a persistent setup; see Persistence.

A port Overcast wants to bind that’s already taken isn’t on this list because it needs no diagnosis: startup fails immediately with the OS’s own bind: address already in use, rather than falling back silently.