Local Development Workflow
Develop against AWS services on your laptop with instant feedback. Typical stacks deploy in seconds instead of minutes, and PERSISTENCE=1 keeps state across restarts so you skip reseeding.
The Problem
The "inner dev loop" for cloud-native applications is broken. Every code change that touches AWS requires a deploy-wait-test cycle:
A typical CloudFormation or Terraform deploy to a real AWS account takes several minutes, sometimes tens of minutes for large stacks. Multiply that by 50 iterations per day and the day evaporates into waiting. Lambda development is the worst case: every code change means repackage, upload, and wait for a cold start, and breakpoints do not work in deployed functions.
minutes
Real AWS deploy cycle
seconds
LocalEmu deploy cycle
The gap is an order of magnitude or two. Exact numbers depend on your stack: the end-to-end tutorials on this site show LocalEmu deploys in the single-to-low-double-digit seconds range for 8 to 19 resources, see the Use Cases index for measured round-trip times per tutorial.
The Solution
LocalEmu gives you a complete AWS environment on your machine. Install it, start it, and develop against real AWS APIs instantly.
$ pip install localemu[runtime]
$ localemu start
# LocalEmu is now running on http://localhost:4566
# Verify it is running
$ awsemu s3 ls
# (empty output -- no buckets yet, but the connection works) The Full Development Loop
The workflow with LocalEmu is: create your infrastructure once, then iterate on your application code as fast as you can type.
# 1. Create your infrastructure
$ awsemu s3 mb s3://my-app-uploads
$ awsemu dynamodb create-table \
--table-name users \
--key-schema AttributeName=id,KeyType=HASH \
--attribute-definitions AttributeName=id,AttributeType=S \
--billing-mode PAY_PER_REQUEST
$ awsemu sqs create-queue --queue-name task-queue
# 2. Run your application (pointing at LocalEmu)
$ AWS_ENDPOINT_URL=http://localhost:4566 python app.py
# 3. Make a change to your code... save the file
# 4. Restart your app (or use a watcher like watchdog)
# 5. Test your change immediately
# 6. Repeat The key difference: steps 3 through 6 take seconds, not minutes. Your infrastructure is already running locally. You just restart your app and test.
Writing Code That Works Everywhere
The only change your application code needs is to respect the AWS_ENDPOINT_URL environment variable. When this variable is set, AWS SDK calls go to LocalEmu. When it is not set, they go to real AWS. Same code, zero branching.
import boto3
import os
def get_dynamodb():
"""Create a DynamoDB resource.
Works with both LocalEmu (dev) and real AWS (prod).
"""
kwargs = {}
endpoint = os.environ.get("AWS_ENDPOINT_URL")
if endpoint:
kwargs["endpoint_url"] = endpoint
return boto3.resource("dynamodb", **kwargs)
def create_user(user_id, name, email):
table = get_dynamodb().Table("users")
table.put_item(Item={
"id": user_id,
"name": name,
"email": email,
})
def get_user(user_id):
table = get_dynamodb().Table("users")
response = table.get_item(Key={"id": user_id})
return response.get("Item") This pattern works in any language. The AWS SDKs for Python, JavaScript, Java, Go, and .NET all support endpoint URL overrides. Set the environment variable in development, unset it in production.
Persistence: Keep State Across Restarts
By default, LocalEmu starts with a clean slate every time. Enable persistence to save and restore your entire AWS state across container restarts.
How PERSISTENCE=1 works
- What is saved: All resources (buckets, tables, queues, functions) and all data (objects, items, messages) are written to disk.
- When it saves: State is periodically saved for any service that has been modified. It also saves on shutdown.
- Where it saves: By default, state is stored in the LocalEmu data directory. When using Docker, mount a volume to persist across container recreations.
- When to use it: During active development when you want to pick up where you left off. Turn it off in CI where you want a clean environment every time.
# Start LocalEmu with persistence enabled
$ PERSISTENCE=1 localemu start
# Create some resources and data
$ awsemu s3 mb s3://my-bucket
$ echo "important data" > /tmp/data.txt
$ awsemu s3 cp /tmp/data.txt s3://my-bucket/data.txt
$ awsemu dynamodb create-table \
--table-name sessions \
--key-schema AttributeName=id,KeyType=HASH \
--attribute-definitions AttributeName=id,AttributeType=S \
--billing-mode PAY_PER_REQUEST
# Stop LocalEmu
$ localemu stop
# ... hours later, or after a reboot ...
# Start LocalEmu again with persistence
$ PERSISTENCE=1 localemu start
# Your data is still there
$ awsemu s3 ls s3://my-bucket/
2026-04-06 10:30:00 15 data.txt
$ awsemu dynamodb list-tables
{"TableNames": ["sessions"]} Persistence is especially valuable during longer development sessions. Instead of re-running your seed scripts after every restart, you just start LocalEmu and continue where you stopped.
When NOT to use persistence
In CI/CD pipelines, you want a clean environment for every run. Do not set PERSISTENCE=1 in your CI workflows. Each test run should start fresh to ensure tests are reproducible and do not depend on leftover state from previous runs.
Docker Compose Setup
For projects with multiple services, use Docker Compose to run your application alongside LocalEmu. This gives every developer on your team the same environment with a single command.
version: "3.8"
services:
localemu:
image: localemu/localemu:latest
ports:
- "4566:4566"
environment:
- PERSISTENCE=1
volumes:
- localemu-data:/var/lib/localemu
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localemu/health"]
interval: 10s
timeout: 5s
retries: 5
app:
build: .
ports:
- "8000:8000"
environment:
- AWS_ENDPOINT_URL=http://localemu:4566
- AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
- AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
- AWS_DEFAULT_REGION=us-east-1
depends_on:
localemu:
condition: service_healthy
worker:
build: .
command: python worker.py
environment:
- AWS_ENDPOINT_URL=http://localemu:4566
- AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
- AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
- AWS_DEFAULT_REGION=us-east-1
depends_on:
localemu:
condition: service_healthy
volumes:
localemu-data: Key details about this setup:
- The
localemu-datavolume preserves state acrossdocker compose downanddocker compose upcycles. - The health check on the LocalEmu container ensures your app does not start before AWS services are ready.
- Inside the Docker network, your app reaches LocalEmu at
http://localemu:4566(the service name), notlocalhost. - Both the
appandworkerservices share the same LocalEmu instance, just like they would share the same AWS account in production.
Seed Scripts: Automate Your Local Setup
A seed script is a shell script (or Python script) that creates all the AWS resources your application depends on: S3 buckets, DynamoDB tables, SQS queues, SNS topics, and any initial data. You run it once after starting LocalEmu, and your entire development environment is ready.
Why seed scripts matter
- No manual setup. Without a seed script, every developer must manually run 10-15 CLI commands to create buckets, tables, and queues before they can start coding. This is tedious, error-prone, and undocumented.
- Reproducible environments. Every team member gets the exact same resources with the exact same names. No "it works on my machine" because someone forgot to create a queue.
- Onboarding in one command. New developers run
bash seed.shand they are ready. No Slack messages asking "which tables do I need?" - Works in CI too. The same seed script that sets up your local environment can run in CI before your test suite starts.
A complete seed script
Here is a bash seed script that creates everything a typical application needs. Every command uses 2>/dev/null || echo "already exists" to make it safe to run multiple times.
#!/bin/bash
# seed.sh - Create all AWS resources your application needs.
# Run this once after starting LocalEmu.
set -euo pipefail
ENDPOINT="http://localhost:4566"
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_DEFAULT_REGION=us-east-1
echo "Creating S3 buckets..."
awsemu s3 mb s3://my-app-uploads 2>/dev/null || echo " bucket my-app-uploads already exists"
awsemu s3 mb s3://my-app-exports 2>/dev/null || echo " bucket my-app-exports already exists"
echo "Creating DynamoDB tables..."
awsemu dynamodb create-table \
--table-name users \
--key-schema AttributeName=id,KeyType=HASH \
--attribute-definitions AttributeName=id,AttributeType=S \
--billing-mode PAY_PER_REQUEST 2>/dev/null || echo " table users already exists"
awsemu dynamodb create-table \
--table-name orders \
--key-schema AttributeName=id,KeyType=HASH \
--attribute-definitions AttributeName=id,AttributeType=S \
--billing-mode PAY_PER_REQUEST 2>/dev/null || echo " table orders already exists"
echo "Creating SQS queues..."
awsemu sqs create-queue --queue-name task-queue 2>/dev/null || echo " queue task-queue already exists"
awsemu sqs create-queue --queue-name notifications 2>/dev/null || echo " queue notifications already exists"
echo "Creating SNS topics..."
awsemu sns create-topic --name order-events 2>/dev/null || echo " topic order-events already exists"
echo "Done. All resources are ready." Running the seed script
Start LocalEmu, then run the script. Two commands and you are ready to develop:
$ localemu start
$ bash seed.sh
Creating S3 buckets...
Creating DynamoDB tables...
Creating SQS queues...
Creating SNS topics...
Done. All resources are ready. Seed script with a Python alternative
If your team prefers Python, use boto3 to do the same thing. The Python version gives you finer control and lets you load sample data in the same script:
#!/usr/bin/env python3
"""Seed script: creates infrastructure and loads test data."""
import boto3
import json
endpoint = "http://localhost:4566"
kwargs = dict(
endpoint_url=endpoint,
aws_access_key_id="AKIAIOSFODNN7EXAMPLE",
aws_secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
region_name="us-east-1",
)
# Create DynamoDB table
ddb = boto3.client("dynamodb", **kwargs)
try:
ddb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
print("Created users table")
except ddb.exceptions.ResourceInUseException:
print("Users table already exists")
# Create S3 bucket
s3 = boto3.client("s3", **kwargs)
try:
s3.create_bucket(Bucket="my-app-uploads")
print("Created my-app-uploads bucket")
except s3.exceptions.BucketAlreadyExists:
print("Bucket already exists")
# Load sample data
ddb_resource = boto3.resource("dynamodb", **kwargs)
table = ddb_resource.Table("users")
for i in range(5):
table.put_item(Item={
"id": f"user-{i}",
"name": f"Test User {i}",
"email": f"user{i}@example.com",
})
print("Loaded 5 sample users") How persistence eliminates repeated seeding
When you enable PERSISTENCE=1, LocalEmu saves all resources and data to disk. After the first run of your seed script, you never need to run it again (until you add new resources). The workflow becomes:
# First time only
$ PERSISTENCE=1 localemu start
$ bash seed.sh
# Every subsequent time: just start LocalEmu
$ PERSISTENCE=1 localemu start
# All buckets, tables, queues, and data are already there The seed script is still idempotent, so running it again is harmless. But with persistence enabled, you skip the setup entirely on most days. This is especially useful for large seed scripts that load thousands of sample records.
Team Onboarding
New team members become productive in minutes, not days. The entire onboarding flow:
$ git clone your-repo
$ docker compose up -d
$ python scripts/seed.py
# Ready to develop. No AWS account needed. Compare this to the traditional onboarding: request AWS access, wait for IT to create an IAM user, configure MFA, set up credentials, learn which resources exist in the dev account, figure out which ones you can touch, deploy your stack, wait 15 minutes, discover a naming conflict with a teammate's resources, fix it, redeploy, wait again.
With LocalEmu, none of that exists. Clone the repo, start the containers, and write code.
Tips for Local Development
Use a .env file
Store your LocalEmu environment variables in a .env file and load them with python-dotenv or your framework's env loader. Keep a .env.example in the repo (without secrets) so teammates know which variables to set.
Work offline
Once LocalEmu is running, you do not need an internet connection. Develop on planes, trains, or anywhere without WiFi. Your entire AWS environment is local.
Use the awsemu CLI
The awsemu CLI is a drop-in replacement for the AWS CLI that points to LocalEmu. Use it to inspect resources, debug state, and run ad-hoc commands during development. See the awsemu CLI docs.
Combine with Terraform
If your infrastructure is defined in Terraform, point it at LocalEmu to provision resources locally. See the Terraform Infrastructure Testing guide.