Moving a million images from one DAM to another is not a one-shot script. It's a phased operation with cutover windows, rollback paths, and verification steps. These seven practices come from real production migrations across thousands of customer workloads.
1. Always start with a dry-run
Before any real migration, run a dry-run pass: enumerate the source, validate destinations, and produce a manifest without writing anything. A dry-run answers four questions:
- How many assets are we actually moving?
- What's the total byte volume?
- Are there any unreadable objects, broken permissions, or malformed metadata?
- Roughly how long will the real migration take?
Univaultport's dry-run mode produces a CSV-exportable manifest in under a minute for libraries up to ~100k assets. Use it as your migration plan of record — share it with stakeholders, get sign-off, then run the real job.
2. Make every migration idempotent
A migration job will be interrupted. The worker crashes, the network blips, the API rate-limits — these are not "if" events, they're "when". Your migration logic must handle being restarted from scratch without corrupting state.
The two key invariants:
1. The same asset migrated twice produces the same destination.
2. A partially-completed migration can be rerun safely.Univaultport handles this for you via per-asset state tracking: every asset is recorded as pending → in-flight → done in Postgres, and reruns skip already-done assets unless you explicitly request a reset. This is the difference between a 12-hour migration that finishes and a 12-hour migration that you have to nervously babysit.
3. Chunk large migrations into bounded job sizes
A single migration job that processes 5 million assets is fragile. A failure 4.5M assets in is genuinely awful to diagnose.
Instead, chunk by source folder or by date range and run a series of smaller jobs:
Source: cloudinary://acme/marketing
├─ Job 1: /2026/01/
├─ Job 2: /2026/02/
├─ Job 3: /2026/03/
└─ ...Each chunk should ideally complete in under 4 hours. Smaller chunks mean:
- Faster failure isolation
- Cleaner rollback (re-run only the failed chunk)
- Easier reasoning about cost (each chunk has known bytes/operations)
- Lower wall-clock to first verified success
4. Respect every platform's rate limits
Every source and destination has API limits. Hit them and you'll see 429s cascading through the worker. Plan your migration speed around the slowest participant:
| Platform | Typical limit | Notes |
|---|---|---|
| Cloudinary | 500 req/hr (free), 2k+ paid | Admin API — listing + delete |
| S3 | 3,500 PUT/s per prefix | Practically unlimited for normal workloads |
| Google Drive | 1,000 req/100s per user | Service account limits per project, not per SA |
| GCS | 5,000 ops/s per bucket | Practically unlimited |
| WordPress | depends on host | Most hosts cap at a few requests per second |
Univaultport's worker applies exponential back-off automatically, but the best mitigation is parallelism control. Set the concurrency to a value that respects the slower platform, not the faster one.
5. Preserve metadata, but verify what gets through
"Migrate the assets" is the easy part. "Migrate the metadata that goes with them" is what makes the migration usable post-cutover.
Each platform has different metadata semantics:
S3 → key, content-type, custom user metadata (limited to 2 KB)
Cloudinary → tags, context, asset folder, transformations
Google Drive → name, parent folders, MIME type, custom properties
WordPress → title, alt-text, caption, description, taxonomies
Contentful → fields per content modelUnivaultport preserves what's lossless across the boundary and logs every lossy translation in the job log. Common examples:
- Cloudinary transformations don't survive a move to S3 (they're applied client-side at delivery time)
- Drive folder hierarchy collapses on WP (flat library)
- AEM XMP metadata gets normalized to a subset on most other destinations
Plan for the lossy pieces before cutover. Decide which platform owns canonical metadata, and migrate references accordingly.
6. Audit-log every migration as it runs
Compliance and ops both need to answer the same questions after the fact:
- Who triggered this migration?
- What credentials were used?
- Which assets moved where, and when?
- Were any assets skipped, failed, or modified?
Univaultport records all of this in a structured, immutable per-job audit log, viewable in the job detail page and exportable as JSON. For SOC 2 / ISO 27001 customers, this log is the artifact your auditors want.
7. Plan the cutover, not just the data move
The data move is the easy half. The hard half is:
- What stops writing to the old system?
- What starts reading from the new one?
- What's the rollback plan if cutover fails?
The pattern that works best for production migrations:
Initial bulk sync
Full migration over a weekend (or whenever traffic is lowest). Source remains the system of record.Delta sync window
Run a daily incremental migration of newly-changed assets for 1–2 weeks. This catches anything the bulk sync missed.Read cutover
Switch your application's reads to the new system. The old system is still being written to.Write cutover
Stop writes to the old system. Final delta sync. Confirm zero drift.Decommission
After 30+ days of verified production use on the new system, archive (don't delete) the old one.
The temptation is always to skip step 5 and just delete the old system. Don't. The cost of keeping an archived copy for 90 days is trivial compared to the disaster of needing it after deletion.
Summary checklist
Before declaring a migration done, you should be able to answer "yes" to all of these:
- Dry-run completed and reviewed
- Migration is idempotent — can be safely restarted
- Job is chunked to < 4 hours per chunk
- Source and destination rate limits respected
- Metadata translation losses documented
- Audit log captures every transfer
- Cutover plan has explicit rollback steps
- Old system archived, not deleted
Where Univaultport fits
Univaultport handles practices 1, 2, 3 (mostly), 4, 5 (lossless parts), and 6 for you automatically. Practices 5 (lossy translation policy) and 7 (cutover plan) are organizational decisions — we give you the data and the logs; you decide the policy.
For most teams, that's the difference between a migration that takes a quarter to plan and weeks to execute, versus one that runs over a weekend with a clear audit trail.



