Storage Migration
When you activate Self Managed Storage for an organization, new files start landing in your bucket immediately. Existing files — anything created before activation — stay where they were originally written. If you want those older files also in your bucket, you run a storage migration.
This page is the operator and admin view of the migration runner: how to start one, what dry-run actually does, how retries work, and what fails closed.
Migrations are started from Settings → Storage → Migrations (cloud, per-organization) or System → Storage → Migrations (Sovereign install admin).
What a migration does
A live migration walks every artifact in the organization that’s still recorded against an older binding, and for each one:
- Reads the object from the source binding using the credentials originally configured for that binding.
- Re-writes the object through the encrypted storage layer to the active binding (your newly-configured bucket).
- Verifies the write by checksum and read-back.
- Updates the artifact’s metadata to point at the new binding.
Only after step 4 is the artifact considered migrated. If any earlier step fails, the artifact stays pointed at the old binding and the item is recorded as failed for that run.
Source objects are not deleted. v1 migration is one-way and additive: files are copied to the new binding, but the original copies stay in the source. Cleanup of the source is your responsibility, on your schedule, in the source storage account. This is intentional — it lets you verify the migration is complete and the new bucket is functional before you delete anything.
Dry-run is mandatory before live
Every migration run requires you to make an explicit dry-run vs live choice. There is no “default” that runs live.
A dry-run:
- Walks the candidate set the same way a live run would.
- Counts how many artifacts would be touched, grouped by source binding.
- Reports the total size to be transferred.
- Mutates nothing. No reads from source, no writes to destination, no metadata changes.
- Is fast and safe to run at any time.
Use the dry-run to:
- See how big the operation is (storage transfer cost, time estimate).
- Confirm the right destination binding is targeted.
- Catch surprises (a binding you forgot about, an unexpected count).
A live run does the actual work and produces real evidence.
Starting a migration
From the Migrations page:
- Click Start migration run.
- Confirm the target binding — this must be the currently-active binding for the organization. You can’t migrate to a binding that isn’t active.
- Pick dry-run or live.
- Provide a reason (audited).
- Submit.
The run appears in the runs list with status Queued, then Planning, then Running. The runner job (storage-object-migration-runner) picks it up on its next cycle and starts processing items.
Run states
A migration run moves through these states:
| State | Meaning |
|---|---|
| Queued | Submitted, waiting for the runner job to pick it up. |
| Planning | The runner is enumerating the candidate items. |
| Running | Items are being processed in batches. |
| Completed | All items in scope finished successfully. |
| Completed with errors | Most items finished, but at least one item permanently failed. The run as a whole is considered complete, but the failed items did not migrate. |
| Failed | The run as a whole could not progress (target binding became unavailable, fatal error in the runner). No further work is happening; the run is closed. |
Within a run, items (individual artifacts) have their own states:
| Item state | Meaning |
|---|---|
| Queued | Awaiting processing. |
| Running | Currently being read/written/verified. |
| Migrated | Successfully migrated to the target binding. |
| Skipped | Not eligible for this run (already on the target binding, or deleted between planning and execution). |
| Failed | Migration of this item failed. The item remains pointed at the source. Cause is recorded. |
Retrying
Two retry shapes:
Retry a failed run
If the run as a whole is in Failed state (for example, the target binding became unavailable mid-run), you can click Retry on the run. This:
- Creates a new run targeting the same binding.
- Re-includes any items that didn’t successfully migrate in the original run.
- Skips items that already migrated.
So retrying a failed run is incremental — you only re-do the work that didn’t get done.
Retry within a partially-successful run
If a run completed with errors but the run as a whole succeeded, the failed items can be retried by starting a new migration run. The new run’s planning phase will see those items as still pointing at the source binding and include them again.
Failure modes
Things that legitimately cause failures and what each one means:
| Symptom | Likely cause |
|---|---|
| Single items fail with a permission error | The recorded credential for the source binding has been rotated or revoked since the binding was set up. Fix the credential on the source binding, retry. |
| Single items fail with a checksum mismatch | The source object was modified externally between the read and the verify. Usually transient; retry. |
| Most items succeed but a few fail with a “not found” error | The source object was deleted between planning and execution. Expected to be rare; the failed items can simply be re-recorded if needed. |
| Whole run fails immediately at planning | The target binding is no longer active (someone activated a different one, or the active binding’s posture sweep failed). Re-verify the target, then start a new run. |
| Whole run fails mid-way | The target binding became unavailable. The run fails closed; nothing falls back to platform storage. See Self Managed Storage. |
Migration never falls back. If the target binding goes unavailable, the run fails and is recorded as failed. Migrated items stay migrated; un-migrated items stay on the source. Novantra will never silently re-route data to its own storage just to make a migration succeed.
Evidence and audit
Every migration produces several evidence streams:
- The route audit entry for the submission (who, when, target binding, dry-run vs live, reason).
- A domain event when the run completes (success or failure shape).
- Per-item records in the run’s items list with state, timestamp, size, and (for failures) cause.
- A security event (
storageOperationsMigrationFailed) if the run hits a fail-closed condition.
You can review all of this from the run’s detail page, and the security event also surfaces in System → Investigations.
Historical bindings remain readable
When you replace the active binding, older bindings don’t go away — they go disabled. A disabled binding:
- Cannot receive new writes.
- Remains fully readable for artifacts still recorded against it.
This means: even if you never migrate the older files, they keep working. The user opening an old file gets it served from wherever it was originally written. Migration is for the case where you want everything in your new bucket — for example, to point a backup process at a single source.
Why migration is one-way in v1
A note on the design choice. Source objects are deliberately not deleted by the migration because:
- It lets you validate the destination before you commit.
- It lets you keep the source as an independent recovery option for a window.
- A delete operation is much more dangerous to automate than a copy.
If you want to clean up the source after a successful migration, do it directly in the source storage account, with your own tools, on your own schedule. The audit history of which artifacts moved is preserved on the Novantra side, so you can always cross-reference.
Cloud and Sovereign differences
The mechanics are identical. The only real difference:
- Cloud organizations submit migrations from inside their workspace settings; the migration runner runs inside Novantra’s cloud infrastructure. Cost of transfer (egress out of Novantra-managed storage into your bucket) follows your contract.
- Sovereign installs run the migration runner locally. Cost of transfer is entirely your network’s.
Both produce the same evidence and follow the same fail-closed rules.
What’s not in v1
Tracked as future work:
- Source deletion. Optional post-migration delete of the source object, with a verification window.
- Per-purpose migration scopes. Currently a run targets the whole organization’s eligible artifacts; you cannot migrate “only forms responses, not uploaded documents.”
- Cross-organization migration. All migration is within a single organization. You cannot migrate artifacts between organizations.
- Throughput controls. Currently the runner uses a fixed batch policy; explicit rate limiting per run is not configurable.
Related
- Self Managed Storage — turning on customer storage in the first place.
- Health & Monitoring — migration failures surface as security events.
- Backup & Restore — your destination bucket should be part of your backup posture.