Beginner5 min read

How to Run DynamoDB Local with Docker — Complete Guide

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:

  • -sharedDb keeps 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 survives docker 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 , 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:

BehaviorDynamoDB LocalAWS DynamoDB
Billing / RCU / WCUNoneMetered per request
ThrottlingNeverYes, at table/index limits
TTL deletion timingBest-effort, not SLA-boundBackground sweeps on AWS schedule
DynamoDB Streams deliverySimplifiedFull stream semantics + Lambda wiring
Transactions across tablesSupported in recent buildsFull ACID with documented limits
Global Tables / PITRNot availableProduction 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:

  1. Unit tests — spin the container in CI, create tables in beforeAll, tear down in afterAll. Keep fixtures small; marshal plain JSON through the DynamoDB JSON converter when tests paste attribute maps by hand.
  2. Integration tests — exercise the same SDK client factory your app uses, swapping only endpoint and credentials. Assert on item shape and conditional writes, not on consumed capacity (Local does not return meaningful ConsumedCapacity for budgeting).
  3. 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 http://localhost:8000. For update-heavy fixtures, assemble the 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.

Updated