Migrating from IBM Cloudant to managed Apache CouchDB: a zero-downtime guide
CouchDB's replication protocol makes leaving any host straightforward in a way that almost no other database can match. There is no pg_dump, no mongoexport, no extraction step at all. You point one CouchDB at another, hit POST /_replicator, and the data flows. The same protocol works between Cloudant and any Apache CouchDB instance, in either direction, which means a Cloudant migration can run as a zero-downtime cutover with rollback that is just as one-sided.
This guide walks the full migration from IBM Cloudant to a managed Apache CouchDB on Layerbase Cloud. It covers the replication setup, what transfers and what does not, how to verify, how to cut over with zero downtime, and how to roll back if something goes sideways.
If you are still deciding whether to leave, the related posts cover the why: Cloudant deprecations in 2025 and 2026 lists what has changed, and Cloudant vs Layerbase Cloud is the head-to-head.
Contents
- Before you start
- Step 1: Provision the target CouchDB
- Step 2: Replicate one database as a smoke test
- Step 3: Replicate every database
- Step 4: Migrate design documents and indexes
- Step 5: Switch to continuous replication for zero downtime
- Step 6: Cut over the application
- Step 7: Verify and decommission Cloudant
- What does not transfer
- Rollback plan
- Migration with
couchbackupas a fallback
Before you start
Gather the following:
- Cloudant credentials with read access to every database you want to migrate. Legacy username and password works, IAM API keys work, the only requirement is that the credentials can read the source databases and
_all_dbs. - An inventory of databases.
GET /_all_dbson Cloudant returns the full list. Save it, you will reuse it. - An inventory of design documents.
GET /{db}/_design_docs?include_docs=trueper database. Design docs replicate normally, but it is worth knowing up front whether any of them use_show,_list,_update, or_rewritehandlers (deprecated and unsupported on Cloudant, but still functional on Apache CouchDB; see the deprecations post). - A list of Cloudant Search indexes. These are Lucene-backed and Cloudant-only. They will not replicate to vanilla Apache CouchDB. See What does not transfer for the workaround.
- A list of IAM API keys your application uses. They will not work against the new instance, and you will need to rotate.
Document counts for each source database, captured before the migration starts, give you a clean before/after to verify against:
for db in $(curl -s "https://$CLOUDANT_AUTH@$CLOUDANT_HOST/_all_dbs" | jq -r '.[]'); do
count=$(curl -s "https://$CLOUDANT_AUTH@$CLOUDANT_HOST/$db" | jq '.doc_count')
echo "$db,$count"
done > cloudant-doc-counts.csvStep 1: Provision the target CouchDB
Create a CouchDB instance on Layerbase Cloud. The dev tier is free, has no database-count cap inside the storage envelope, and runs the upstream Apache CouchDB binary (so design documents, Mango queries, MapReduce views, and the replicator all behave the same as on any other Apache CouchDB host).
Grab the connection URL from the instance page. It looks like:
https://admin:GENERATED_PASSWORD@your-host.cloud.layerbase.devConfirm it responds:
curl https://admin:GENERATED_PASSWORD@your-host.cloud.layerbase.dev/{"couchdb":"Welcome","version":"3.5.0","features":["access-ready","partitioned","reshard","scheduler"],"vendor":{"name":"The Apache Software Foundation"}}Export it for the rest of the steps:
export TARGET="https://admin:GENERATED_PASSWORD@your-host.cloud.layerbase.dev"
export SOURCE="https://APIKEY:PASSWORD@your-cloudant.cloudantnosqldb.appdomain.cloud"Step 2: Replicate one database as a smoke test
Pick the smallest database first. The goal of this step is to flush out any auth or networking issues before you commit to a long-running batch.
curl -X POST $TARGET/_replicator \
-H "Content-Type: application/json" \
-d "{
\"_id\": \"migrate-smoketest\",
\"source\": \"$SOURCE/smalldb\",
\"target\": {
\"url\": \"$TARGET/smalldb\",
\"headers\": {}
},
\"create_target\": true,
\"continuous\": false
}"Watch progress:
curl $TARGET/_scheduler/docs/_replicator/migrate-smoketestWhen state returns "completed", compare counts:
curl -s $SOURCE/smalldb | jq '.doc_count'
curl -s $TARGET/smalldb | jq '.doc_count'These should match. If they do not, the most common causes are documents with conflicts (replicated, but counted differently) or partial reads against Cloudant Search-backed views (which are not part of _all_docs and therefore not a discrepancy, just confusing).
Step 3: Replicate every database
Once the smoke test passes, do the rest. For a small fleet, a loop over your saved _all_dbs list works:
while read -r db; do
curl -X POST $TARGET/_replicator \
-H "Content-Type: application/json" \
-d "{
\"_id\": \"migrate-$db\",
\"source\": \"$SOURCE/$db\",
\"target\": {\"url\": \"$TARGET/$db\"},
\"create_target\": true,
\"continuous\": false
}"
done < db-list.txtFor larger fleets (dozens or hundreds of databases), use IBM's couchreplicate CLI. It parallelises with a bounded concurrency, retries on failure, and renders a TUI progress bar. The same tool runs unmodified against any source-target pair, not just Cloudant-to-Cloudant.
npm install -g couchreplicate
couchreplicate -s "$SOURCE" -t "$TARGET" -c 4The -c 4 flag is concurrency. Four parallel replications is a safe starting point. Bump it if Cloudant tolerates it (watch for 429: too many requests from the source side; that is your signal to back off).
Step 4: Migrate design documents and indexes
Design documents replicate as regular documents, so they come across as part of step 3. There is no separate "index" migration step in CouchDB, the indexes are derived from the design documents and rebuild on first query against the new host.
Two things to be aware of:
- First-query latency is real. A freshly-replicated database has design docs but no built indexes. The first Mango query that hits a new index pays the build cost. For a large database this can be seconds to minutes. The fix is to warm the indexes after replication and before cutover:
for view in mango-index-1 mango-index-2; do
curl -X POST $TARGET/yourdb/_find \
-H "Content-Type: application/json" \
-d "{\"selector\": {\"_id\": {\"\$gt\": null}}, \"limit\": 1, \"use_index\": \"$view\"}"
done- Cloudant Search indexes do not transfer. These are the Lucene-backed full-text indexes you create through Cloudant's
_searchendpoint. They live outside the CouchDB replication protocol and are a Cloudant-only feature. Migration options are:
- Rebuild as Mango indexes if the queries are structured rather than full-text.
- Rebuild as full-text in a separate engine (Meilisearch is a good fit and is also available on Layerbase Cloud). Stream document changes from CouchDB into the search engine through the
_changesfeed. - Self-host an Apache CouchDB build that ships with Lucene (some 3.x packagings include it; the standard Apache binary does not).
Step 5: Switch to continuous replication for zero downtime
The one-shot replications from step 3 leave you with two databases that are point-in-time identical. For a zero-downtime cutover, switch them to continuous mode so the target keeps catching up while the application is still writing to Cloudant.
For each database, post a new replicator doc with continuous: true:
curl -X POST $TARGET/_replicator \
-H "Content-Type: application/json" \
-d "{
\"_id\": \"sync-$db\",
\"source\": \"$SOURCE/$db\",
\"target\": {\"url\": \"$TARGET/$db\"},
\"continuous\": true
}"Verify everything is in running state:
curl -s $TARGET/_scheduler/docs/_replicator | jq '.docs[] | {id: .id, state: .state}'You can leave continuous replication running for hours or days. It is idempotent, restartable, and survives target restarts.
Step 6: Cut over the application
When you are ready:
- Quiesce writes to Cloudant. Either deploy a maintenance-mode flag in your app, route writes to the new endpoint, or accept a short read-only window.
- Wait for the replicator to drain. Watch the
_scheduler/docsoutput untilinfo.changes_pending(or equivalent) is zero for every replication. - Stop the continuous replications.
DELETE /_replicator/sync-$db. - Verify final document counts match the source one more time, including any documents written in the last minute.
- Flip the application's connection string to the new host, redeploy, and resume writes.
Total downtime if you do this carefully is whatever your deploy pipeline takes, often seconds.
Step 7: Verify and decommission Cloudant
Run the count script from the Before you start section again, against the Layerbase target. Diff against the pre-migration CSV:
diff cloudant-doc-counts.csv layerbase-doc-counts.csvFor deeper verification, spot-check a few documents by _id:
for id in $(shuf -n 20 sample-doc-ids.txt); do
cloudant_doc=$(curl -s "$SOURCE/yourdb/$id")
target_doc=$(curl -s "$TARGET/yourdb/$id")
diff <(echo "$cloudant_doc") <(echo "$target_doc")
doneThe _rev values should match exactly. If they do not, the source had writes the replicator did not pick up, which means you cut over before the continuous replication caught up.
Once you are satisfied, leave the Cloudant instance running for a week or two as a hot fallback. You are still paying for it during that window, but the cost of an unplanned rollback during the first week post-cutover usually exceeds the marginal Cloudant bill. After that, delete the Cloudant databases (carefully, ideally one at a time with at least a day between each in case the application surfaces a missed dependency) and finally the instance.
What does not transfer
A short list of things that will not come across automatically and need a per-case decision:
| Item | Reason | Action |
|---|---|---|
| Cloudant Search indexes | Lucene, Cloudant-only feature | Rebuild as Mango or as a separate full-text engine. |
| IAM API keys | Cloudant-account-scoped | Generate new credentials on the target, update app config. |
| Cloudant-specific monitoring and dashboards | Tied to IBM Cloud | Re-create on your new host's observability stack. |
| Per-account capacity and rate-limit configuration | Cloudant Standard plan concept, does not exist on Apache CouchDB | Drop. Apache CouchDB does not throttle the same way. |
CouchApp _show, _list, _update, _rewrite handlers using QuickJS-incompatible JS | The design docs replicate, but the JS engine on Apache CouchDB may still be SpiderMonkey | Test on the target. Most CouchApps work unchanged. See the deprecations post for context. |
Things that do transfer cleanly, in case you were wondering:
- All JSON documents and their full revision history.
- Design documents and the MapReduce views they define.
- Mango indexes (the JSON-only ones, not Lucene-backed
_search). - Document attachments (
_attachments), via the standard replicator. - Conflicts, with both winning and losing revisions preserved.
Rollback plan
The same protocol that gets you onto Layerbase gets you back to Cloudant if something is wrong. Two options:
- Continuous replication in reverse. Before cutover, also set up continuous replication from Layerbase back to Cloudant. After cutover, Cloudant stays warm with the new writes. If you need to roll back, flip the connection string and the data is already there.
curl -X POST $SOURCE/_replicator \
-H "Content-Type: application/json" \
-d "{
\"_id\": \"reverse-$db\",
\"source\": \"$TARGET/$db\",
\"target\": \"$SOURCE/$db\",
\"continuous\": true
}"- Catch-up replication on demand. If you skipped the reverse-replication and need to roll back, run a one-shot from Layerbase to Cloudant before flipping the connection string. This costs a few minutes of replication time, which is fine for an unplanned rollback.
Both options exist because CouchDB's replication is symmetric. The "source" and "target" are conventions, not roles.
Migration with couchbackup as a fallback
If your network policy does not allow direct CouchDB-to-CouchDB replication (for example, a corporate proxy that blocks one of the endpoints), you can fall back to IBM's couchbackup tool. It dumps a Cloudant database to a local JSON-Lines file, which you then restore against any CouchDB.
npm install -g @cloudant/couchbackup
# Backup
couchbackup --url "$SOURCE" --db yourdb > yourdb-backup.txt
# Restore against the target
cat yourdb-backup.txt | couchrestore --url "$TARGET" --db yourdbA few caveats:
couchbackupdoes not back up attachments. If your databases use attachments, replication is the only sensible path.- Document conflicts are preserved by default. The
--mode shallowflag drops them for speed. - The output is large but compresses well. Pipe through
gzipif you are storing the dump.
Direct replication is faster, simpler, and lossless. Use couchbackup only if direct replication is blocked.
Where to go from here
- Provision the target in the next 60 seconds: Create a CouchDB on Layerbase Cloud.
- Read about what changed in Cloudant that prompted this migration: IBM Cloudant deprecations in 2025 and 2026.
- See a feature-by-feature comparison: Cloudant vs Layerbase Cloud.
- Browse other managed CouchDB options: Cloudant alternatives.
Migrating off Cloudant is mostly a matter of running POST /_replicator once per database and verifying the doc counts. Continuous replication makes the cutover zero-downtime, and the same protocol works in reverse if you need to roll back. There is no other database where the migration story is this clean.