Import and anonymisation
dynoxide import loads a DynamoDB Export (the S3 export format, JSON Lines) into a Dynoxide database. It's how you get a realistic local dataset without hand-writing fixtures, and the anonymisation rules mean production data can be masked on the way in rather than after it has already landed on your laptop.
It runs in one of two modes. File mode (--output) writes a SQLite file, vacuums it, and optionally compresses it. Serve mode (--serve or --mcp) imports into memory and starts a server on top of it, leaving nothing on disk.
Basic import
dynoxide import \
--source ./export-data/ \
--schema schema.json \
--output snapshot.db
--source points at a directory following the DynamoDB Export layout:
export-data/
├── Users/
│ └── data/
│ └── 00000000.json.gz
└── Orders/
└── data/
└── 00000000.json.gz
--schema takes DescribeTable JSON, which is what describe-table gives you:
aws dynamodb describe-table --table-name Users > schema.json
Restrict which tables come across with --tables:
dynoxide import --source ./export/ --schema schema.json --output snapshot.db \
--tables Users,Orders
Anonymisation
Rules live in a TOML file. Each rule picks the items it applies to with match, names the attribute with path, and says what to do with action:
[[rules]]
match = "attribute_exists(email)"
path = "email"
action = { type = "fake", generator = "safe_email" }
[[rules]]
match = "attribute_exists(phone)"
path = "phone"
action = { type = "mask", keep_last = 4, mask_char = "*" }
[[rules]]
match = "attribute_exists(ssn)"
path = "ssn"
action = { type = "hash", salt_env = "ANON_SALT" }
[[rules]]
match = "attribute_exists(notes)"
path = "notes"
action = { type = "redact" }
[consistency]
fields = ["userId", "email"]
ANON_SALT=my-secret-salt dynoxide import \
--source ./export/ \
--schema schema.json \
--rules rules.toml \
--output anonymised.db
Actions
| Action | What it does |
|---|---|
fake |
Replaces the value with generated data. Generators: safe_email, name, first_name, last_name, phone_number, address, company_name, sentence, word |
mask |
Keeps the last N characters and masks the rest. Takes keep_last and mask_char |
hash |
SHA-256 with a salt read from an environment variable. salt_env is required |
redact |
Replaces the value with [REDACTED] |
null |
Replaces the value with NULL |
Keeping values consistent
Anonymising each attribute independently breaks joins: the same user ID becomes two different values in two tables, and the data stops making sense. Fields listed under [consistency].fields get the same anonymised value everywhere they appear in a single import run.
That works because hash is deterministic - same input plus same salt gives the same output. It also means the salt is what protects the data: reuse a salt across runs and the values stay stable across runs too, which is useful for reproducible fixtures and a liability if the salt leaks. Keep it in an environment variable, and out of the rules file.
Serve mode
Import straight into memory and serve it. Nothing is written to disk, so the data disappears with the process:
# HTTP server on the imported data
dynoxide import --source ./export/ --schema schema.json --serve --port 8000
# stdio MCP server, for a coding agent
dynoxide import --source ./export/ --schema schema.json --mcp
# Both at once
dynoxide import --source ./export/ --schema schema.json --serve --mcp --mcp-port 8100
--serve and --mcp both conflict with --output. Used alone, --mcp starts a stdio MCP server; combined with --serve it starts an HTTP one on --mcp-port (8100 by default). Anonymisation applies the same way, so --rules masks the data before it reaches the in-memory database.
Pairing serve mode with --mcp-read-only gives an agent a realistic, anonymised dataset it cannot write to.
Other options
| Flag | Description |
|---|---|
--force |
Overwrite an existing output file |
--compress |
Compress the output with zstd, producing snapshot.db.zst (requires --output) |
--continue-on-error |
Carry on when a batch fails instead of aborting |
Every flag is in the CLI reference.
Streams and imported data
If the --schema file carries a StreamSpecification, streams are enabled on the imported table automatically. Imported items themselves generate no stream records - bulk import bypasses recording for speed - so recording starts with the first write after the import completes. See Streams.