TL;DR:

  • LocalStack emulates AWS services locally in Docker, so your code can talk to S3, Lambda, DynamoDB, SQS, and more without hitting a real AWS account.
  • The Community edition is free and covers the services most teams use daily; LocalStack Pro adds more services and enterprise features.
  • It significantly speeds up development feedback loops and removes the need for dev/staging AWS accounts with real costs.
  • The main limitation is that it’s an emulation, not the real thing — some edge cases and newer AWS features behave differently or aren’t supported.

AWS is the infrastructure for a huge proportion of production systems, but developing against it directly is slow and expensive. Every Lambda test run that deploys to a real account, every S3 bucket you create for development, every DynamoDB table you spin up — all add cost and latency to your workflow. For CI pipelines running hundreds of times a day, even small per-test AWS costs add up.

LocalStack runs a local version of AWS inside a Docker container. Your code talks to localhost:4566 instead of *.amazonaws.com, and it behaves (mostly) the same. No AWS account needed, no egress charges, sub-millisecond latency for S3 operations, and your integration tests can run entirely offline.

Getting Started

LocalStack runs as a Docker container. The quickest way to start:

docker run --rm -p 4566:4566 localstack/localstack

That’s it. The Community edition is now running with support for S3, DynamoDB, SQS, SNS, Lambda, IAM, CloudFormation, and other core services.

For development workflows, the docker-compose approach is more convenient:

# docker-compose.yml
services:
  localstack:
    image: localstack/localstack
    ports:
      - "4566:4566"
    environment:
      - SERVICES=s3,dynamodb,sqs,lambda
      - DEBUG=1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

The SERVICES variable restricts which services start — useful if you only need a subset, since starting fewer services speeds up initialisation.

Talking to LocalStack

LocalStack exposes all services through a single endpoint at port 4566. Your AWS SDK just needs the endpoint URL overridden and dummy credentials (LocalStack doesn’t validate them in the Community edition):

Python (boto3):

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:4566",
    aws_access_key_id="test",
    aws_secret_access_key="test",
    region_name="us-east-1",
)

s3.create_bucket(Bucket="my-dev-bucket")
s3.put_object(Bucket="my-dev-bucket", Key="test.txt", Body=b"hello")

Node.js (AWS SDK v3):

import { S3Client, CreateBucketCommand } from "@aws-sdk/client-s3";

const client = new S3Client({
  endpoint: "http://localhost:4566",
  region: "us-east-1",
  credentials: { accessKeyId: "test", secretAccessKey: "test" },
  forcePathStyle: true, // required for LocalStack S3
});

await client.send(new CreateBucketCommand({ Bucket: "my-dev-bucket" }));

The forcePathStyle: true setting is important for S3 — it uses localhost:4566/bucket-name URLs rather than bucket-name.localhost:4566 which requires DNS configuration.

The AWS CLI with LocalStack

The awslocal wrapper (install with pip install awscli-local) adds --endpoint-url=http://localhost:4566 to every command automatically:

awslocal s3 mb s3://test-bucket
awslocal s3 cp ./myfile.txt s3://test-bucket/
awslocal dynamodb list-tables
awslocal sqs create-queue --queue-name my-queue

This is useful for manual inspection and for writing init scripts that seed LocalStack with fixtures before tests run.

Lambda Development with LocalStack

LocalStack runs Lambda functions inside Docker containers — the same execution environment AWS uses. For Python and Node.js lambdas, the feedback loop is fast: update the code, invoke the function, see the result.

# Create a simple Lambda
awslocal lambda create-function \
  --function-name my-function \
  --runtime python3.12 \
  --handler handler.lambda_handler \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --zip-file fileb://function.zip

# Invoke it
awslocal lambda invoke \
  --function-name my-function \
  --payload '{"key": "value"}' \
  output.json && cat output.json

The main limitation here is cold start times in LocalStack can be slower than real AWS for compiled runtimes (Go, Java, Rust). For hot path development this usually isn’t a problem.

Integration Testing in CI

Where LocalStack really earns its place is in CI pipelines. Instead of integration tests that hit real AWS services (with the cost, latency, and cleanup complexity that involves), your tests spin up LocalStack, run against it, and tear it down.

With pytest and the localstack-utils or moto libraries, or just direct boto3 calls with the endpoint override, the pattern is:

  1. CI starts LocalStack via Docker Compose
  2. Test fixtures create required resources (S3 buckets, DynamoDB tables, SQS queues)
  3. Tests run against LocalStack
  4. Everything is torn down automatically when the container stops

No cleanup scripts, no shared state between test runs, no cross-contamination between parallel CI jobs. Each run starts from a clean slate.

What’s in Community vs Pro

The Community edition (free, Apache 2.0) covers the services most teams use: S3, DynamoDB, SQS, SNS, Lambda, API Gateway, IAM, CloudFormation, EventBridge, SecretsManager, and others.

LocalStack Pro (paid, subscription-based) adds services like RDS, ElasticSearch, Cognito, and CloudFront, along with persistence (state survives container restarts), a web UI for inspecting resources, and cloud pods (snapshots of LocalStack state you can share across a team).

For most development and testing use cases, the Community edition is sufficient. Pro is worth considering if you’re using services that aren’t in Community, or if state persistence between dev sessions matters for your workflow.

Limitations to Know

LocalStack is an emulation, and that matters in some situations:

Eventual consistency: Some DynamoDB behaviours around consistency models may differ subtly from the real service.

IAM enforcement: Community edition doesn’t enforce IAM permissions. If your code’s correctness depends on specific permission boundaries, you won’t catch IAM issues in LocalStack Community.

Newer features: AWS releases new features constantly. LocalStack Pro tracks more of them, but there will always be a lag. Check the LocalStack feature coverage matrix before assuming a specific AWS behaviour is emulated.

Performance characteristics: Network latency, throughput limits, and DynamoDB capacity behaviour don’t match real AWS. Don’t use LocalStack for load or performance testing.

For correctness testing of your application logic, LocalStack is excellent. For production parity testing of AWS-specific behaviours, you still need a real AWS account — typically a dedicated testing account with tight IAM constraints.

A Practical Setup

The pattern that works well for most teams: LocalStack in Docker Compose for local development, the same Docker Compose config in CI, and real AWS accounts (with restricted IAM roles) for integration and staging environments that need to test real AWS behaviour.

This keeps the development and unit/integration testing loop fast and free, while reserving real AWS usage for the environments that actually need it.