# DynamoDB

> zop.dev provisions DynamoDB tables per namespace and mints a credential chain alongside each one: an IAM policy scoped to the cluster-namespace table prefix, an IAM user with an access key, and Secrets Manager entries. billing_mode is the cost lever, and the hash and range key schema is fixed for life. Changing keys means table number 2 and a migration.

Source: https://zop.dev/integrations/provision/aws/dynamodb
Updated: 2026-08-19

---

Amazon DynamoDB is AWS's serverless NoSQL key-value database. zop.dev provisions tables as namespace-scoped resources and generates least-privilege credentials automatically, so each environment can only reach its own tables.

## Tables plus a scoped IAM policy, user, and secret

DynamoDB tables defined per namespace, a per-namespace IAM policy scoped to table/\<cluster>-\<ns>*, an IAM user with access key, and Secrets Manager entries carrying the credentials.

## Key schema, billing_mode, and index settings

```text
Per-table options: hash_key, range_key, key types, billing_mode, read/write capacity, TTL, and global_secondary_index[].
```

## Attached through the namespace dynamo_db map

Provisioned through the namespace module's dynamo_db map rather than as a standalone top-level resource.

## Every table ships with an IAM user

Attaching DynamoDB through a namespace does more than create tables. The module mints a
per-namespace IAM policy scoped to `table/<cluster>-<ns>*`, an IAM user, an access key for that
user, and Secrets Manager entries carrying the credentials. Four identity objects arrive with
the table, and none of them were in the request.

The scoping is the point: the resource pattern means one namespace's credentials cannot read
another namespace's tables, because the prefix does not match. But the access key is a
long-lived static credential: it never expires on its own, and rotating it is on you. It also
lands in Secrets Manager, where every entry bills monthly.

## The key schema cannot be revised

`hash_key`, `range_key`, and their types are fixed at table creation. DynamoDB has no
alter-table for keys: realizing the partition key should have been `tenant_id` rather than
`user_id` means creating a second table and migrating every item. Global secondary indexes are
the partial escape (`global_secondary_index[]` entries can be added later to query on other
attributes), but the base schema is permanent.

The table name is equally fixed, and because the IAM policy is scoped to the cluster-namespace
prefix, the namespace's claim on it is too.

## billing_mode decides how you pay

Provisioned capacity bills for the read and write units you declare, continuously, whether or
not a single request arrives. A table sized for a launch spike keeps billing at spike rates
until someone lowers it. On-demand bills per request and costs nothing at idle. Each global
secondary index carries its own capacity on top of the table's, so an indexed table multiplies
whichever choice you made.

TTL is the free lever: expired items are deleted at no charge, which keeps storage from growing
without bound on event-shaped data.
