Adopt MongoMig on an existing database¶
Situation: your app has been running for a while. The database already has collections, indexes and data, and you never used migrations. You want MongoMig to track changes from now on without touching what's there.
1. Initialise and describe your models¶
$ mongomig init
Initialised MongoMig in /srv/myapp
+ mongomig.yaml
+ migrations/env.py
+ migrations/schema_snapshot.json
+ migrations/versions/
Next steps:
1. export MONGODB_URI=mongodb://localhost:27017
2. mongomig revision -m "initial"
3. mongomig current
Register your models in migrations/env.py (import app.models) and describe them as they
are today:
from datetime import datetime
from pydantic import BaseModel
from mongomig import Index, collection
@collection("customers", indexes=[Index("email", unique=True, name="customers_email_unique")])
class Customer(BaseModel):
name: str
email: str
phone: str | None = None
created_at: datetime
$ mongomig models
customers app.models.Customer
_id objectId
name string
email string
phone string | null default=None
created_at date
indexes: customers_email_unique (email ↑, unique)
storage profile: python, by_alias
2. Baseline¶
$ mongomig baseline
Created baseline revision 708558de8f9e → migrations/versions/20261003_1852_708558de8f9e_baseline.py
snapshot: 1 collection(s): customers
Next: `mongomig upgrade` (marks the baseline applied; changes nothing).
The baseline revision is empty. It records "the models as they are now" in
schema_snapshot.json, so future diffs compare against today's state instead of treating
everything as new.
$ mongomig upgrade
Running upgrade <base> -> 708558de8f9e, baseline
✓ done in 0ms
Applied 1 revision(s).
Run mongomig upgrade once in every environment (dev, staging, production). It changes
nothing in the database; it only records the baseline.
3. See how well the data matches the models¶
The models describe what the code expects. Years of data rarely match exactly:
$ mongomig inspect customers
Collection: customers
Documents: 10,000 random sample of ~20,000 (0.06s)
Fields (nested presence is relative to the parent object):
field presence types
_id 100.00% objectId
name 100.00% string
email 100.00% string
created_at 100.00% date 86.0% · string 14.1%
phone 24.78% string
vip 1.86% bool
Indexes:
_id_ (_id ↑)
customers_email_unique (email ↑, unique)
Validator: none
Based on 10,000 sampled documents; documents outside the sample may differ. Use --full-scan for a complete analysis.
$ mongomig drift
CUSTOMERS 10,000 sampled of ~20,000 documents
✗ created_at expected date · observed string 14.0% > 0.5%
hint: convert the values in a migration, or widen the model's type
✗ vip not in the model · present in 2.02% of documents > 1%
hint: add it to the model, or remove it: ctx.ops.unset_field(..., backup=True)
2 failed, 0 below threshold
Based on samples: documents outside the sample may differ. Use --full-scan for an exact answer.
That's a to-do list, not an emergency:
created_atstored as strings (14%) by an older app version → convert them with a migration, as in Change a field's type;vipexists in 2% of documents but not in the model → addvip: bool = Falseto the model, or remove it as in Remove a field.
Pick the right storage profile
If most dates (or ObjectIds) are strings, your app probably writes documents with
jsonable_encoder / model_dump(mode="json"). Set storage="json" in env.py
before the baseline; drift hints at this when it sees it.