overcast local AWS emulator

Service Reference

EC2 — Elastic Compute Cloud

EC2 uses the AWS Query protocol (form-encoded POST, XML responses). Operations are identified by the Action parameter with API version 2016-11-15.

EC2 — Elastic Compute Cloud

AWS docs: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Welcome.html

EC2 uses the AWS Query protocol (form-encoded POST, XML responses). Operations are identified by the Action parameter with API version 2016-11-15.

The default VPC

Each region seeds a default VPC the first time something reads VPCs or subnets in it — IsDefault: true, 172.31.0.0/16, a default subnet per availability zone, an attached internet gateway, a main route table with the default route, and a default security group. That is the set DescribeVpcs --filters Name=isDefault,Values=true and CDK’s Vpc.fromLookup(isDefault: true) read.

Its backing network is not a per-VPC bridge: it is the shared data plane (OVERCAST_NETWORK), which is where every container that named no VPC already sits. “No VPC” and “the default VPC” are therefore the same place, by construction rather than by coincidence.

Two consequences follow from that network being the emulator’s own:

  • DeleteVpc on the default VPC removes the record and leaves the network. AWS allows the delete, but the network has every container Overcast started attached to it.
  • Attaching or detaching an internet gateway on it is ignored with a warning. The toggle recreates the network, which would sever every attached container.

When Docker is available, each non-default VPC is backed by a real Docker bridge network. The VPC’s CIDR block maps to the Docker subnet, and the network’s isolation mode (--internal) reflects whether an internet gateway is attached. When Docker is unavailable, VPC operations are metadata-only.

On startup, the EC2 service reconciles its stored VPC state against actual Docker networks — recreating missing networks, updating drifted IDs, and removing orphaned networks that no longer match a stored VPC. Docker network lifecycle events (create, destroy, connect, disconnect) are forwarded through the event bus.

Docker network conventions

LabelValuePurpose
overcast.managedtrueIdentifies Overcast-managed nets
overcast.serviceec2Service that owns the network
overcast.resource-idVPC ID (e.g. vpc-abc)Links network back to the VPC
overcast.vpc-idVPC IDAdditional VPC lookup convenience

Network naming: overcast-vpc-{vpcID}.

Advanced: VPC networking strategies

Real AWS allows overlapping VPC CIDR blocks in the same account/region. Docker bridge networks on one host do not: overlapping subnets collide at the kernel routing table level. Overcast exposes a strategy switch so users can choose the behavior that best matches their workflow.

Configure with OVERCAST_EC2_VPC_STRATEGY:

StrategyBehaviorBest For
shared (default)Overlapping VPCs reuse a single Docker network; NetworkStatus=shared.Most local-dev setups where convenience matters more than strict isolation.
strictOverlapping CreateVpc requests fail with InvalidVpc.Range; conflicting persisted VPCs are marked NetworkStatus=conflict.Teams that want loud failures on accidental overlap.
remappedOverlapping VPCs get a unique Docker shadow subnet in 100.64.0.0/10; NetworkStatus=remapped and DockerCidrBlock records the shadow CIDR.Multi-VPC simulations that need overlap without Docker subnet collisions.

DescribeVpcs includes synthetic tag overcast:network-status=<value> and /_overcast/debug/ec2/vpcs exposes internal fields (NetworkStatus, DockerNetworkID, DockerCidrBlock) for diagnostics.

Important caveat for remapped: data-plane packet routing still follows Docker’s real subnet assignment. API metadata keeps the user-requested CidrBlock, but workloads that hardcode raw private IPs are less portable than DNS-based SDK flows.

netns is reserved for future work and is currently rejected at startup with a configuration error.


Limitations and divergences from AWS

The VPC emulation provides enough structure for CDK deployments and SDK-based workflows, but several aspects differ materially from real AWS networking:

Networking model

  • No real IP routing between subnets. On AWS, subnets within a VPC can route to each other via the implicit local route. In Overcast, each VPC is a single flat Docker bridge network — all containers in the same VPC can reach each other, but there is no per-subnet isolation or inter-subnet routing. The CIDR blocks are recorded as metadata but do not partition Docker’s address space.
  • No NAT gateway, VPN gateway, or transit gateway data plane. NAT gateways and VPN gateways are emulated as metadata only (state and associations tracked, but no real NAT or VPN routing). Only internet gateways affect the Docker network topology. Attaching an IGW toggles the Docker network between --internal (isolated) and normal bridge mode (host-routable).
  • Elastic IPs are metadata-only. EIPs can be allocated, associated, and released, but the synthetic IPs assigned are not routable. Containers receive Docker-assigned IPs only.
  • VPC peering is metadata-only. The state machine (pending-acceptanceactivedeleted) is emulated, but no cross-network Docker routing is established. Containers in peered VPCs cannot actually communicate through the peering connection.
  • Route tables are metadata-only. Routes are stored and returned correctly in API responses, but they do not affect Docker packet routing. The CreateRoute, AssociateRouteTable, and DisassociateRouteTable operations are recorded but have no effect on traffic.
  • CDK subnet lookup metadata. CloudFormation-created VPC resources preserve EC2 tags, DescribeSubnets returns subnet tagSet, and DescribeRouteTables returns NAT gateway routes so CDK can classify private subnet groups during VPC lookups. This metadata does not imply NAT data-plane routing.

Filters

Every Describe* refuses a filter name it does not implement, with AWS’s InvalidParameterValue: The filter '<name>' is invalid. The error goes on to name every filter that operation does support, and the same sets are in the Notes column of the endpoint table below.

That is stricter than AWS in one direction: AWS refuses a name it does not model, and Overcast additionally refuses a name AWS models but Overcast has not implemented. It is deliberate. A filter that is accepted and then ignored answers a question the emulator could not answer — a describe-vpcs filtered on tag:Name that returns every VPC in the region reads as “your VPC exists” to a find-or-create script, which then adopts the wrong one. An error costs a minute; a wrong answer costs an afternoon. If you hit one, drop the filter or narrow the call by resource ID.

A filter name is matched exactly, as AWS matches it — Name=VPC-ID is refused, because real EC2 refuses it too.

A filter value is a pattern, as on AWS: * stands for any run of characters including none, ? for exactly one, and a backslash escapes either so you can ask for a literal one.

aws ec2 describe-vpcs        --filters 'Name=tag:Name,Values=overcast-*'
aws ec2 describe-images      --filters 'Name=name,Values=Amazon Linux 2*'
aws ec2 describe-subnets     --filters 'Name=availability-zone,Values=us-east-1?'

Filters are AND-ed with each other and the values within one are OR-ed, as on AWS, and a <Resource>Id.N parameter is AND-ed with them.

Security groups

  • Security group rules are metadata-only. Ingress/egress rules are stored and returned in DescribeSecurityGroups, but they are not enforced at the Docker network level. All containers on the same Docker network can communicate freely regardless of security group rules. This matches the common local-dev use case where you want connectivity, not firewall testing.

Instances

  • EC2 instances are metadata-only. RunInstances creates state records with async pendingrunning transitions, but no actual VMs or containers are launched. Instance metadata (IDs, state, security groups, subnet placement) is tracked for API compatibility with CDK and Terraform, but there is no compute behind it.

Lambda VPC integration

  • Lambda containers are connected to the VPC’s Docker network (in addition to the control plane) when a function has a VpcConfig. This provides real connectivity between Lambda and other containers on the same VPC network (e.g. RDS, ECS tasks). However, subnet-level and security-group-level isolation is not enforced — a Lambda connected to one subnet can reach resources in any other subnet within the same VPC network.

General

  • No DHCP option sets beyond a default stub response.
  • No NACLs (Network ACLs). Only security groups are emulated (as metadata).
  • No VPC Flow Logs.
  • Docker dependency. All networking features degrade gracefully to metadata-only when Docker is not available. API responses remain correct; only actual container-level connectivity is lost.

Advanced: VPC networking strategies

TL;DR — most users can skip this section. The default works unless you’re intentionally creating VPCs with overlapping CIDRs.

The problem

In real AWS, every VPC is an isolated virtual network. Two VPCs in the same account can legally share or overlap CIDRs (10.0.0.0/16 twice is perfectly valid) — the only time overlap matters is when you try to connect them via peering, Transit Gateway, or a VPN.

Overcast backs each VPC with a Docker bridge network so that real containers (Lambda, ECS, RDS) launched into a VPC can actually talk to each other. But every Docker bridge on a host shares a single kernel routing table. The Linux networking stack flat-out refuses to have two bridges claiming overlapping subnets — it returns Pool overlaps with other one on this address space. That’s the fundamental impedance mismatch: AWS’s VPC model assumes per-VPC isolation, and Docker’s default bridge driver assumes host-global uniqueness.

Overcast can’t make that go away. Instead it offers a strategy knob so you can pick how the emulator should behave when the two models disagree, set via the OVERCAST_EC2_VPC_STRATEGY environment variable.

Strategies

StrategyStatusBehaviour on overlapping CIDRs
shared (default)✅ ImplementedVPCs with the same CIDR share a single Docker network. Container isolation between sharers is not enforced.
strict⏳ Not yet — falls back to sharedReject overlapping CIDRs at CreateVpc. Startup always tolerates pre-existing overlaps (first-one-wins, losers marked conflict).
remapped⏳ Not yet — falls back to sharedAllocate a shadow /16 from 100.64.0.0/10 when the requested CIDR collides. API responses still show the user’s CIDR.
netns⏳ Not yet — falls back to sharedPer-VPC Linux network namespace. Real overlap with real isolation. Requires root / CAP_NET_ADMIN.

Values other than shared are accepted today but log a warning at startup and fall back to shared. The design for each is captured below.

shared — the default

  • What it does. For each distinct CIDR in your stored VPCs, Overcast creates exactly one Docker bridge network. Additional VPCs requesting the same CIDR reuse that network and are marked NetworkStatus=shared. Reconcile on startup deterministically picks one owner per CIDR group (sorted by VpcID), adopts existing networks by label or IPAM subnet before creating anything new, and removes networks that no VPC references.
  • When it’s fine (the common case — single VPC, or multiple VPCs with non-overlapping CIDRs): shared behaves byte-identically to strict because no collisions exist to share. You pay zero cost.
  • When to pick a different one: you’re running workloads that actually test container-to-container isolation between VPCs that share a CIDR. Under shared, a container in vpc-A (10.0.0.0/16) can reach a container in vpc-B (10.0.0.0/16) because they’re on the same bridge. That’s wrong in real AWS, and shared doesn’t pretend otherwise. If you care, wait for remapped or netns.
  • On CreateVpc failure modes. If Docker is unavailable the VPC is stored with NetworkStatus=unbacked and reconcile picks it up later. If Docker is available but the create fails, we log and still store the VPC — the API call succeeds, the network is best-effort.
  • On DeleteVpc. The Docker network is only torn down when the VPC being deleted was the last one using it. Deleting a sharer leaves the owner’s network alone.
  • On IGW attach/detach. Toggling a VPC’s --internal flag requires recreating the backing Docker network. shared refuses to do this when the network is shared (it would affect every sharer), logs a Warn, and leaves the existing network in place.

strict (planned)

  • What it will do. CreateVpc rejects any CIDR that overlaps an existing VPC with InvalidVpc.Range. Startup reconcile never fails — VPCs whose CIDR collides with another existing VPC are marked NetworkStatus=conflict and refused for container-backed operations (RunInstances, CreateDbInstance, etc.) with a clear emulator error.
  • When you’d use it. You want loud, early failure on accidental overlap — ideal for CI pipelines or tests where overlapping CIDRs signal a bug in your IaC, not an intended configuration.
  • When not to use it. You’re running CDK apps or CloudFormation templates that legitimately create overlapping CIDRs (multi-account simulation, dev/prod parity tests). They’ll fail at deploy.

remapped (planned)

  • What it will do. When a new VPC’s CIDR collides, Overcast silently carves a shadow /16 out of 100.64.0.0/10 (CGNAT space), stores it as DockerCidrBlock, and creates the Docker network there. DescribeVpcs and every other API response still reports the user’s CidrBlock. A translation layer converts between fabricated and real IPs for PrivateIpAddress fields, ENI descriptions, etc.
  • When you’d use it. You’re running CDK or Terraform workloads where overlap is expected and you rely on API responses matching the CIDR you asked for. Highest fidelity.
  • When not to use it. Your containers talk to each other by raw private IP (hardcoded in config files, not resolved via DNS). The fabricated IPs will not be reachable — only the shadow addresses are real. Workloads that use service discovery, ENI DNS, or RDS/ELB endpoint DNS are unaffected.

netns (planned, speculative)

  • What it will do. Create containers with --network=none and move their veth into a per-VPC Linux network namespace with its own bridge and routing table. Each netns has an independent address space, so 10.0.0.0/16 in vpc-A is genuinely unrelated to the same CIDR in vpc-B.
  • When you’d use it. You need real AWS-grade VPC isolation with real overlap support. The only option that’s faithful to both the AWS model and the network behaviour simultaneously.
  • When not to use it. You’re not running overcastd as root inside a container with CAP_NET_ADMIN. The netns plumbing Docker doesn’t expose requires elevated privileges that most dev setups don’t grant. Also: it’s a substantially heavier code path than the other three, so the performance overhead is real.

Picking a strategy

SituationUse
Single VPC, or multiple non-overlapping CIDRsshared (default)
CI that should fail loudly on accidental CIDR collisionsstrict (today: fallback to shared)
CDK/TF apps with legitimate overlapping CIDRs that care about API-visible IPsremapped (today: fallback to shared)
Testing real container-level VPC isolation with overlapping CIDRsnetns (today: fallback to shared)

Why shared is the default

The overwhelmingly common Overcast workload is one VPC, or a handful of VPCs with distinct CIDRs. In both cases shared never triggers the sharing code path and is indistinguishable from a hypothetical “perfectly isolated” implementation. Users who don’t hit the edge case pay nothing. Users who do hit it get silent, working behavior with a documented isolation compromise — instead of the alternative (a noisy reconcile error every startup) which is what overcast did before strategies existed.

Inspecting network state

Each VPC carries a NetworkStatus value that tells you what the active strategy decided:

ValueMeaning
okThis VPC owns its backing Docker network.
sharedThis VPC reuses a Docker network owned by another VPC (shared mode).
unbackedNo Docker network (Docker was unavailable, or the last create failed).
conflictReserved for strict mode — CIDR collided with another existing VPC.
remappedReserved for remapped mode — backed by a shadow CIDR.

NetworkStatus is persisted on each VPC record and written into the startup reconcile logs (reconcile networks: …). Debug-endpoint and web UI surfacing is planned alongside the future strategies — see the strict, remapped, and netns sections above.

Summary

Category✅ Supported
General69
VPC network states3

Endpoints

General

OperationStatusNotesAWS Docs
AcceptVpcPeeringConnection✅ SupportedTransitions from pending-acceptance to activedocs
AllocateAddress✅ SupportedGenerates eipalloc- ID and synthetic public IP; Domain honoured; supports TagSpecificationdocs
AssociateAddress✅ SupportedAssociates EIP with instance; generates eipassoc- IDdocs
AssociateRouteTable✅ SupportedAssociates route table with subnetdocs
AttachInternetGateway✅ SupportedToggles VPC Docker network from --internal to external (bridge)docs
AttachVpnGateway✅ SupportedMetadata-only VPC attachmentdocs
AuthorizeSecurityGroupEgress✅ Supporteddocs
AuthorizeSecurityGroupIngress✅ SupportedIpPermissions with protocol, ports, CIDR rangesdocs
CreateInternetGateway✅ SupportedGenerates igw-xxx IDdocs
CreateKeyPair✅ SupportedGenerates dummy fingerprint and key materialdocs
CreateNatGateway✅ SupportedRequires subnet and EIP; supports TagSpecificationdocs
CreateNetworkInterface✅ SupportedRequires subnet; assigns synthetic private IPdocs
CreateRoute✅ SupportedDestinationCidrBlock + GatewayId or NatGatewayIddocs
CreateRouteTable✅ SupportedVPC must exist; auto-creates local routedocs
CreateSecurityGroup✅ SupportedDefault egress allow-all rule added on createdocs
CreateSubnet✅ SupportedVPC must exist; honors AvailabilityZone; defaults to region+“a”docs
CreateTags✅ SupportedTag any resource by ID; visible to that resource’s own describedocs
CreateVpc✅ SupportedCidrBlock required; creates Docker bridge network (--internal unless IGW attached) and main route tabledocs
CreateVpcEndpoint✅ SupportedMetadata-only; Gateway and Interface types accepted; state always “available”docs
CreateVpnGateway✅ SupportedMetadata-only; type ipsec.1 with AmazonSideAsndocs
CreateVpcPeeringConnection✅ SupportedBoth VPCs must exist; starts in pending-acceptance statedocs
DeleteInternetGateway✅ SupportedMust be detached firstdocs
DeleteKeyPair✅ SupportedIdempotent (no error if not found)docs
DeleteNatGateway✅ SupportedMarks as deleteddocs
DeleteNetworkInterface✅ Supporteddocs
DeleteRoute✅ SupportedRemoves route by RouteTableId + DestinationCidrBlockdocs
DeleteRouteTable✅ SupportedCannot delete main route tabledocs
DeleteSecurityGroup✅ SupportedFails with DependencyViolation while attached to a running/pending instance; the VPC’s default group answers CannotDeletedocs
DeleteSubnet✅ SupportedFails with DependencyViolation while ENIs, non-terminated instances, or NAT gateways remain in the subnetdocs
DeleteTags✅ SupportedRemove tags by key; a resource’s tags are removed with the resourcedocs
DeleteVpc✅ SupportedRemoves Docker network; fails with DependencyViolation while subnets, gateways, endpoints, peering connections, ENIs, or instances remaindocs
DeleteVpcEndpoints✅ SupportedAccepts VpcEndpointId.N; silently skips unknown IDsdocs
DeleteVpnGateway✅ SupportedRequires gateway to be detacheddocs
DeleteVpcPeeringConnection✅ SupportedFrom active or pending-acceptance; transitions to deleteddocs
DescribeAccountAttributes✅ SupportedHardcoded defaults (supported-platforms, max-instances…)docs
DescribeAddresses✅ SupportedSelects by AllocationId.N; returns tags; Filters: allocation-id, association-id, domain, instance-id, network-interface-id, private-ip-address, public-ipdocs
DescribeAvailabilityZones✅ Supported3 AZs per region (a, b, c); Filters: region-name, state, zone-namedocs
DescribeDhcpOptions✅ SupportedReturns a fabricated default DHCP options set; Filters: nonedocs
DescribeImages✅ SupportedHardcoded set of 4 AMIs (AL2, Ubuntu, Windows, AL2023); selects by ImageId.N; Filters: architecture, description, image-id, image-type, is-public, name, owner-id, root-device-type, state, virtualization-typedocs
DescribeInstanceTypes✅ SupportedHardcoded set: t3.micro/small/medium, m5.large/xlarge; Filters: current-generation, instance-type, memory-info.size-in-mib, vcpu-info.default-vcpusdocs
DescribeInstances✅ SupportedSelects by InstanceId.N; Filters: availability-zone, image-id, instance-id, instance-state-code, instance-state-name, instance-type, placement.availability-zone, subnet-id, vpc-id, tag:, tag-key, tag-valuedocs
DescribeInternetGateways✅ SupportedSelects by InternetGatewayId.N; Filters: attachment.state, attachment.vpc-id, internet-gateway-id, tag:, tag-key, tag-valuedocs
DescribeKeyPairs✅ SupportedSelects by KeyName.N; Filters: fingerprint, key-name, key-pair-iddocs
DescribeNatGateways✅ SupportedSelects by NatGatewayId.N; Filters: nat-gateway-id, state, subnet-id, vpc-id, tag:, tag-key, tag-valuedocs
DescribeNetworkInterfaces✅ SupportedSelects by NetworkInterfaceId.N; Filters: availability-zone, description, mac-address, network-interface-id, status, subnet-id, vpc-id, tag:, tag-key, tag-valuedocs
DescribeRegions✅ SupportedHardcoded list of 8 regions; Filters: endpoint, opt-in-status, region-namedocs
DescribeRouteTables✅ SupportedSelects by RouteTableId.N; includes NAT gateway routes; Filters: association.main, association.route-table-association-id, association.subnet-id, route-table-id, vpc-id, tag:, tag-key, tag-valuedocs
DescribeSecurityGroups✅ SupportedSelects by GroupId.N; Filters: description, group-id, group-name, vpc-id, tag:, tag-key, tag-valuedocs
DescribeSubnets✅ SupportedSelects by SubnetId.N; includes tagSet for CDK subnet groups; Filters: availability-zone, cidr-block, state, subnet-id, vpc-id, tag:, tag-key, tag-valuedocs
DescribeTags✅ SupportedFilters: key, resource-id, resource-type, valuedocs
DescribeVpcAttribute✅ SupportedReturns the stored enableDnsSupport or enableDnsHostnames valuedocs
DescribeVpcEndpoints✅ SupportedSelects by VpcEndpointId.N; Filters: service-name, vpc-endpoint-id, vpc-endpoint-state, vpc-endpoint-type, vpc-iddocs
DescribeVpnGateways✅ SupportedSelects by VpnGatewayId.N; Filters: amazon-side-asn, attachment.state, attachment.vpc-id, availability-zone, state, type, vpn-gateway-id, tag:, tag-key, tag-valuedocs
DescribeVpcPeeringConnections✅ SupportedSelects by VpcPeeringConnectionId.N; Filters: accepter-vpc-info.vpc-id, requester-vpc-info.vpc-id, status-code, vpc-peering-connection-iddocs
DescribeVpcs✅ SupportedSelects by VpcId.N; Filters: cidr, isDefault, state, vpc-id, tag:, tag-key, tag-valuedocs
DetachInternetGateway✅ SupportedToggles VPC Docker network back to --internaldocs
DetachVpnGateway✅ SupportedMetadata-only VPC detachmentdocs
DisassociateAddress✅ SupportedBy AssociationIddocs
DisassociateRouteTable✅ SupportedCannot disassociate main associationdocs
ModifyInstanceAttribute✅ SupportedInstanceType.Value persisted; all other attributes accepteddocs
ModifySubnetAttribute✅ SupportedMapPublicIpOnLaunch is persisted and returned by DescribeSubnetsdocs
ModifyVpcAttribute✅ SupportedEnableDnsSupport, EnableDnsHostnames are persisted and returned by DescribeVpcAttributedocs
ReleaseAddress✅ SupportedBy AllocationIddocs
RevokeSecurityGroupEgress✅ Supporteddocs
RevokeSecurityGroupIngress✅ Supporteddocs
RunInstances✅ SupportedMinCount/MaxCount, TagSpecifications, async pending→running; each state emits an EC2 Instance State-change Notification to the default EventBridge busdocs
StartInstances✅ SupportedFrom stopped state only; each state emits an EC2 Instance State-change Notification to the default EventBridge busdocs
StopInstances✅ SupportedFrom running state only; async stopping→stopped; each state emits an EC2 Instance State-change Notification to the default EventBridge busdocs
TerminateInstances✅ SupportedAsync shutting-down→terminated transition; each state emits an EC2 Instance State-change Notification to the default EventBridge busdocs

VPC network states

OperationStatusNotesAWS Docs
unbacked✅ SupportedNo Docker network (Docker unavailable, or the last create failed)docs
conflict✅ SupportedReserved for strict mode when CIDR collides with another existing VPCdocs
remapped✅ SupportedReserved for remapped mode and backed by a shadow CIDRdocs