Understanding the connection paths to LocalStack: port 4566 as the gateway, Docker host versus bridge networks, access from other containers, and endpoint configuration for the AWS CLI, modern SDKs, and boto3.

In episode 12 your state is safe from restarts. But there's a fundamental question we haven't fully dissected: how does your code actually connect to LocalStack? So far we've been writing http://localhost:4566 without a second thought. In production, applications run in a container separate from the emulator — and that's where most networking confusion lies.
In this episode you'll understand port 4566, the difference between Docker host and bridge networks, how to connect other containers, and endpoint configuration for the AWS CLI, modern SDKs, and boto3.
Since the modern version, LocalStack exposes one port for all services: 4566. The per-service port era (4572 for S3, 4570 for DynamoDB, and so on) is over. A single HTTP gateway sorts requests by API path.
From the host, the address is http://localhost:4566. A health check is available at http://localhost:4566/_localstack/health:
curl -s http://localhost:4566/_localstack/health | jqThe response contains the service list and their statuses — a quick way to verify the emulator is alive before debugging code.
Docker containers run on a network. Two modes are relevant:
| Mode | Concept | localhost from the app |
|---|---|---|
| Host | Shares the host network namespace | Yes, directly |
| Bridge | Each container has its own IP | No — must use the service name |
The host mode (network_mode: host) makes the container share the host's network stack, so localhost:4566 works directly. Convenient on Linux, but not on Docker Desktop (macOS/Windows) because Docker runs inside a VM — localhost points to the VM, not the container.
The bridge mode is the docker-compose default: each service gets an internal IP and is accessed by its service name. This is the dominant mode for real development.
The key to bridge: use the service name, not localhost. If compose defines a localstack service, then from another service the endpoint is http://localstack:4566:
services:
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
app:
build: .
depends_on:
- localstack
environment:
- AWS_ENDPOINT_URL=http://localstack:4566
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_DEFAULT_REGION=us-east-1Port 4566:4566 stays open to the host so you can use awslocal from the terminal. Meanwhile, app talks to the emulator via http://localstack:4566 — Docker DNS translates the service name into the container IP.
Tip
Always set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION in other services. Modern SDKs read these variables for credentials and region — in the emulator, the fake test/test values are enough.
Previously, each SDK had its own way of pointing at an endpoint: endpoint_url for boto3, endpoint for SDK v2. Now AWS_ENDPOINT_URL is the cross-SDK standard — boto3 (since 1.34.64) and the AWS SDK v3 read it automatically:
export AWS_ENDPOINT_URL=http://localhost:4566
aws s3 lsThere are also per-service variants: AWS_ENDPOINT_URL_SQS, AWS_ENDPOINT_URL_DYNAMODB, and more — useful when a single global endpoint isn't enough.
awslocal is an AWS CLI wrapper that adds --endpoint-url=http://localhost:4566 and fake credentials automatically:
awslocal s3 ls
aws --endpoint-url=http://localhost:4566 s3 lsThe two commands above are equivalent. awslocal saves typing and avoids endpoint typos. Remember: this is only for the AWS CLI, not for SDKs.
For Python, boto3 accepts endpoint_url per client:
import boto3
s3 = boto3.client(
"s3",
region_name="us-east-1",
aws_access_key_id="test",
aws_secret_access_key="test",
endpoint_url="http://localhost:4566",
)Because this parameter is identical between LocalStack and AWS, production code doesn't need to change — just set endpoint_url when initializing the client. Don't hardcode the value in code: read it from an environment variable so production keeps using the real AWS endpoint.
Docker Compose provides internal DNS: each service name resolves directly to its container IP address on the same network. That's what makes http://localstack:4566 work without extra configuration. If services live in different compose files, join them with a shared network (external: true). See the active networks with docker network ls.
Warning
Don't use localhost inside a container to reach the emulator — it points to the container itself. Always use the service name (localstack) when running on a bridge network.
| Context | Endpoint |
|---|---|
| Host terminal (awslocal, curl) | http://localhost:4566 |
| App in another container | http://localstack:4566 |
| Inside the emulator container itself | http://localhost:4566 |
| SDK v3 / modern boto3 | AWS_ENDPOINT_URL env var |
Summary of this episode:
4566 serves all services; health check at /_localstack/health.localhost; bridge network uses the service name.http://localstack:4566 with AWS_ENDPOINT_URL.awslocal is equivalent to aws --endpoint-url=http://localhost:4566 for the AWS CLI.endpoint_url or the standard env var.The connection path is now clear on the networking side. Only one security layer is left to discuss: who's allowed to access the emulator and what can they do? In episode 14 we dissect IAM & Authentication — fake credentials, policy evaluation, and its limitations in LocalStack. See you there!