Real DNS: resolve Route 53 records for local containers
Run fakecloud as a DNS resolver so records created in Route 53 actually resolve for your containers, with no dnsmasq or /etc/hosts layer.
A local multi-service setup often needs real DNS: service A reaching service B by hostname, or an app doing an A/CNAME/MX/TXT lookup for a record it expects. You can create the record in fakecloud's Route 53, but by default nothing answers the lookup, so people bolt on a separate dnsmasq or /etc/hosts layer -- a second source of truth beside the Route 53 records already declared.
Start fakecloud with --dns and it runs an actual DNS resolver (UDP + TCP) that answers straight from the Route 53 zones and records you created. Point a container's resolver at fakecloud and a normal lookup resolves to the local endpoint. Route 53 becomes the one source of truth -- used exactly like production. It is the companion to instance/task credentials: credentials and DNS are the two environmental surfaces that let a real app run locally, unmodified.
Enable the resolver
# Port 53 needs root; use a high port for an unprivileged run.
fakecloud --dns --dns-addr 127.0.0.1:15353Create a zone and a record as usual, then resolve it over real DNS:
ID=$(aws --endpoint-url http://localhost:4566 route53 create-hosted-zone \
--name example.com --caller-reference $(date +%s) \
--query 'HostedZone.Id' --output text)
aws --endpoint-url http://localhost:4566 route53 change-resource-record-sets \
--hosted-zone-id "$ID" --change-batch '{
"Changes":[{"Action":"CREATE","ResourceRecordSet":{
"Name":"api.example.com","Type":"A","TTL":60,
"ResourceRecords":[{"Value":"10.0.0.5"}]}}]}'
dig @127.0.0.1 -p 15353 api.example.com A # -> 10.0.0.5Answered types: A, AAAA, CNAME, MX, TXT, NS, PTR, SPF, CAA. CNAMEs are chased into local zones, so a query for an alias returns the CNAME plus the records resolved at its target (address records are forward-resolved even when the target is an external name). Zone selection is a longest-suffix match across every account's hosted zones.
Names outside Route 53 forward upstream
So a container can use fakecloud as its sole resolver, any name that falls in no Route 53 zone is forwarded to an upstream resolver:
dig @127.0.0.1 -p 15353 registry-1.docker.io A # -> forwarded, real answerThe upstream defaults to the first non-loopback nameserver in /etc/resolv.conf, falling back to 8.8.8.8:53. Override it with --dns-upstream 1.1.1.1:53 (or FAKECLOUD_DNS_UPSTREAM).
Point containers at fakecloud (docker-compose)
Run fakecloud where containers can reach it (here on a fixed address in the compose network) and set it as the dns: server for the services that should resolve your Route 53 records. Point A records at addresses reachable on the local network (container IPs or the host):
services:
fakecloud:
image: faiscadev/fakecloud
command: ["--dns", "--dns-addr", "0.0.0.0:53"]
networks:
appnet:
ipv4_address: 172.28.0.53
ports:
- "4566:4566"
app:
image: my-app
dns:
- 172.28.0.53 # resolve via fakecloud's Route 53
networks: [appnet]
networks:
appnet:
ipam:
config:
- subnet: 172.28.0.0/16Create api.example.com -> 172.28.0.x (a container's address) in Route 53 and app reaches it by name. Non-Route 53 names (package registries, APIs) still resolve because fakecloud forwards them upstream.
Notes:
- Binding port 53 needs root (or
CAP_NET_BIND_SERVICE); in a container fakecloud runs as its own process so0.0.0.0:53is fine. For a host run without root, use a high port and point clients at it. - To resolve container names (rather than IPs you set as record values), keep Docker's embedded DNS in the loop: put a
CNAMEin Route 53 pointing at the container name and let the container's own resolver chain handle it, or set the record's value to the container's address. - Wildcard record sets (
*.example.com) are matched at a single label level (the common case). Weighted / latency / geo / failover routing policies are not evaluated: every matching record set is returned. Single-label*.example.comwildcards work; multi-level wildcards andNAPTR/DS/ANYqueries are not served. Negative answers (NXDOMAIN/NODATA) do not carry the zoneSOAin the authority section, so downstream resolvers cannot negatively-cache them. This is a local development resolver, not a full authoritative/recursive nameserver.
Assert resolution from a test (no socket)
The same resolution logic is exposed over HTTP so a test can check what the resolver would answer without binding a UDP port (handy when --dns is off or port 53 is unavailable in CI):
curl 'http://localhost:4566/_fakecloud/dns/resolve?name=api.example.com&type=A'
# { "name": "api.example.com", "type": "A", "status": "ANSWERED",
# "authoritative": true,
# "records": [ { "name": "api.example.com", "type": "A", "ttl": 300, "value": "172.28.0.10" } ] }type defaults to A and accepts A, AAAA, CNAME, MX, TXT, NS, PTR, SPF, CAA, SRV, SOA; an unsupported type returns 400. status is one of ANSWERED, NODATA (name exists in a local zone but has no record of that type), NXDOMAIN (name is inside an authoritative local zone but has no records of any type), or NOT_AUTHORITATIVE (name is outside every Route 53 zone, so the --dns resolver would forward it upstream). When an A/AAAA query's CNAME chain leaves every local zone, the response reports the CNAME record and sets external_cname to the target the socket resolver would forward-resolve upstream (this HTTP endpoint does no upstream I/O, so it names the target rather than returning a synthesized address). Every test-assertion SDK wraps this as dnsResolve / dns_resolve:
res = fc.dns_resolve("api.example.com", "A")
assert res.status == "ANSWERED"
assert res.records[0].value == "172.28.0.10"