DynamoDB Local is AWS's downloadable emulation of DynamoDB in a single process —
same API, no AWS account, no network, no per-request bill. Use it for local
development and integration tests, then point the same code at the cloud in
production. It ignores provisioned throughput and never throttles, so it can't
stand in for load or limit testing.
How do I run DynamoDB Local with Docker?
Run docker run -p 8000:8000 amazon/dynamodb-local to start the official image,
which exposes the DynamoDB engine on http://localhost:8000. Point your AWS SDK
or CLI at that endpoint with any dummy credentials, then create tables and run
requests exactly as you would against the cloud. Add -sharedDb and a mounted
-dbPath volume to keep data across restarts.
Start the container
docker run -p 8000:8000 amazon/dynamodb-local
That exposes the engine on http://localhost:8000.
docker-compose
Most projects pin it in docker-compose.yml so the whole team gets the same
endpoint:
services:
dynamodb:
image: amazon/dynamodb-local
user: root
command: '-jar DynamoDBLocal.jar -sharedDb -dbPath /data'
ports:
- '8000:8000'
volumes:
- dynamodb-data:/data
volumes:
dynamodb-data:
The image runs as the non-root dynamodblocal user, which can't open a
database file inside the root-owned named volume — without user: root you hit
SQLiteException [14] unable to open database file and every call hangs.
Persistence
By default DynamoDB Local is in-memory — every table vanishes when the
container stops. Two flags make it durable:
-
-sharedDbkeeps all clients on one shared database file (without it, each set of credentials/region gets its own isolated DB — a common "where did my table go?" surprise). -
-dbPath /data+ a mounted volume writes that file to disk, so data survivesdocker compose down.
Point the SDK at it
Only the endpoint changes — credentials can be any dummy values:
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({
endpoint: 'http://localhost:8000',
region: 'local',
credentials: {accessKeyId: 'x', secretAccessKey: 'x'}
});
Create a table
aws dynamodb create-table \
--endpoint-url http://localhost:8000 \
--table-name AppData \
--attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S \
--key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
A single-table PK/SK schema like this is a good
default. When you load fixtures, convert plain JSON to the wire format with the
DynamoDB-JSON converter.
Verify the container is up and the table landed:
aws dynamodb list-tables --endpoint-url http://localhost:8000
Browse it with a GUI
CLI calls get tedious fast. The usual options are the open-source dynamodb-admin
web UI or a desktop client. DynoTable connects straight to
localhost:8000 (or any LocalStack endpoint — see
connecting to DynamoDB Local & LocalStack)
and lets you browse, query with the SQL Workbench, and edit local tables with the
same UI you use for cloud tables — no aws CLI round-trips.
What Local does not emulate
Treat Local as an API compatibility layer, not a capacity simulator. It ignores
provisioned throughput, never returns
ProvisionedThroughputExceededException,
and does not model on-demand burst behavior. A load test against Local tells you
nothing about partition limits or adaptive capacity in AWS.
Other gaps show up in integration tests if you do not plan for them:
| Behavior | DynamoDB Local | AWS DynamoDB |
|---|---|---|
| Billing / RCU / WCU | None | Metered per request |
| Throttling | Never | Yes, at table/index limits |
| TTL deletion timing | Best-effort, not SLA-bound | Background sweeps on AWS schedule |
| DynamoDB Streams delivery | Simplified | Full stream semantics + Lambda wiring |
| Transactions across tables | Supported in recent builds | Full ACID with documented limits |
| Global Tables / PITR | Not available | Production features |
If your test asserts throttling, TTL expiry within seconds, or stream fan-out,
run at least one suite against a disposable cloud table or LocalStack with the
features you need enabled.
A practical local workflow
Most teams wire Local into three layers:
-
Unit tests — spin the container in CI, create tables in
beforeAll, tear down inafterAll. Keep fixtures small; marshal plain JSON through the DynamoDB JSON converter when tests paste attribute maps by hand. -
Integration tests — exercise the same SDK client factory your app uses,
swapping only
endpointand credentials. Assert on item shape and conditional writes, not on consumed capacity (Local does not return meaningfulConsumedCapacityfor budgeting). - Manual exploration — connect DynoTable with a Local profile, stage edits, and run PartiQL or key-condition queries before you deploy schema changes.
When you outgrow a single process — multiple services, S3 triggers, or IAM-style
routing — graduate to LocalStack or a
dev account. Local stays the fastest loop for "does my access pattern compile?"
Seed data without hand-marshalling
Loading ten fixture items from a JSON file is faster when you do not tag every
value yourself. Paste the array into the
DynamoDB JSON converter, copy the marshalled
output, and batch-write with BatchWriteItem against --endpoint-url. For update-heavy fixtures, assemble the
http://localhost:8000
UpdateExpression in the
DynamoDB expression builder and paste the
generated attribute maps into your test harness.
DynoTable's item editor performs the same marshalling on commit — useful when a
test failure leaves you staring at raw {"S":...} blobs in the CLI.
When to leave Local
Ship to a real table when you need any of the following measured on AWS itself:
- Capacity planning — a 1 KB item queried 1,000 times per second consumes roughly 250 eventually-consistent RCU per second on on-demand billing; Local reports zero. Model that with the pricing calculator using sizes from the item-size calculator.
- Index propagation lag — GSI reads are eventually consistent in production; Local returns index rows quickly enough that stale-read bugs hide until deploy.
- Cross-account IAM — resource-scoped roles and condition keys only exist in the cloud.
Keep Local for fast feedback on schema and expression syntax; validate cost and
consistency assumptions against a staging table before production traffic.
Pitfalls worth scripting around
-
Forgotten
-sharedDb— each unique credential pair gets an isolated database; CI and your laptop look like different universes. -
Root-owned volume without
user: root— the SQLite backend fails silently until you add the compose override from the section above. - Assuming Streams parity — stream-enabled Lambdas need a cloud or LocalStack target; Local alone will not exercise fan-out.
- Empty-string keys — allowed on non-key attributes since 2020, still rejected on keys; validate fixtures the same way you would in AWS.
Download DynoTable, add a profile pointed at http://localhost:8000,
and browse the tables you just created — the same grid, filter builder, and SQL
Workbench you use in production, with zero AWS spend on the loop.
Top comments (0)