Imports
Move an existing Postgres database into CapyDB without rebuilding your app around a new platform.
How an import works
An import takes a source Postgres connection URL and loads that database into your CapyDB project as a job:
{
"source_url": "postgres://user:password@source-host:5432/db?sslmode=require",
"recreate": true
}capydb import --source-url "postgres://..." --recreate --wait- The source must be publicly reachable — private/internal addresses are rejected. TLS is preferred automatically (
sslmode=requireis added unless the source explicitly opts out). recreate: truedrops and recreates the target database before importing, giving you a clean slate. Without it, the import loads into the existing database — fine for a first import into an empty project, asking for conflicts otherwise.import.completedfires as a webhook event when the job finishes, and connected deployment integrations re-push env vars.
Preflight: find out before, not during
POST /v1/projects/{id}/imports/preflight (or the Import panel in the dashboard) connects to the source, inspects it, and grades the import without mutating anything:
capydb import preflight --source-url "postgres://..."Three checks, each pass / warn / fail:
| Check | Fails when |
|---|---|
source_size_within_plan | The source database is larger than the project plan's storage limit. Warns above 80%. |
source_version_compatible | The source runs a newer Postgres major than the target (downgrades are not supported — logical dumps flow forward, not backward). Targets run Postgres 17. |
extensions_available_on_target | The source uses extensions outside the target's 13-extension allowlist (which includes pgvector and PostGIS) — the failing check names the offenders. |
The worker re-runs the same gate before any destructive import step, so a source that drifted between preflight and import still gets caught before damage is done.
Importing a dump file
Some providers don't allow external connections to their Postgres instances (or only over a private network), so a connection-URL import can't reach them. For those, export a dump locally and upload it instead:
pg_dump -Fc --no-owner -d "postgres://...source..." -f app.dump
capydb import --file app.dump --recreate --waitThe CLI requests a one-hour presigned upload URL, streams the file directly to CapyDB's object storage (with a progress line on stderr), then starts the import referencing the uploaded object. The dashboard's Import panel offers the same flow: switch the source mode to Upload dump file, pick the file, and start the import once the upload finishes. Uploaded dumps are deleted from storage automatically after a successful import.
Two formats are accepted, detected automatically:
- Custom format (
pg_dump -Fc) — recommended. Restored withpg_restorein a single transaction, with the same extension handling as URL imports. - Plain SQL (
pg_dumpwithout-Fc, or any.sqlfile). Replayed withpsqlin a single transaction. Prefer-Fc: plain dumps that carry ownership or privilege statements for roles that don't exist on CapyDB will abort the import.
One caveat: dump-file imports validate size only. There is no live source to inspect, so the version/extension preflight doesn't apply — the upload is rejected up front if the dump file alone already exceeds your plan's storage limit, and because dumps are compressed, a dump that fits may still restore to a database that doesn't. The post-import storage check on the database host is the backstop; an import that overflows the plan limit fails and rolls back.
If you can connect to the source directly, prefer the connection-URL import — it gets the full preflight.
When to use imports
- Move an existing app from another Postgres host (see the per-provider playbooks below)
- Load a sanitized copy of an existing environment
- Rebuild a project database from a known source
Migration playbooks
Provider-specific instructions, including where each provider hides the connection string:
Post-import checklist
Every playbook ends with the same four steps; they are worth internalizing:
- Sequences — verify sequence values survived (they do in a normal logical dump, but check the tables that matter:
SELECT last_value FROM your_seq;). - Extensions — confirm
\dxlists what your app expects. ANALYZE— run it once so the planner has statistics for the freshly loaded data.- Env cutover — point
DATABASE_URL/DATABASE_URL_UNPOOLEDat CapyDB, deploy, and watch the first minutes in Observability.
Downtime honesty
An import is a point-in-time copy. Writes that land on the source after the import starts are not carried over — logical replication for zero-downtime cutovers is not supported yet. The realistic low-downtime sequence is:
- Run the preflight ahead of time and fix whatever it flags.
- Pick a quiet window. Put the app in maintenance mode or stop writers.
- Run the import with
--recreate --wait. - Cut the env vars over and resume traffic.
For most databases under the plan size limits, the window is minutes, not hours.