E-NO Logo
EN FR
MongoDB upgrade 8 Min Read

MongoDB upgrade and migration with practical examples: practical implementation guide

calendar_today Published: 2026-07-21
update Last Updated: 2026-07-21
analytics SEO Efficiency: 100%
Technical guide illustration for MongoDB upgrade and migration with practical examples: practical implementation guide.

Upgrading or migrating MongoDB should be boring: predictable plans, repeatable checks, and safe fallbacks. This guide gives you a practical path to:

  • Plan a version upgrade or data migration with low risk
  • Back up and rehearse restores
  • Validate changes with code and queries
  • Roll back cleanly if something fails

You will see a production-oriented workflow and a local pilot you can run with Docker, Node.js, and a minimal Express API.

Workflow Overview

The safest way to upgrade is to split the work into clear, checkable steps. Treat each step as a pass or fail gate and proceed only when the previous step is green.

1) Define scope and success

  • Target: server version(s), driver versions, and any schema or index changes
  • Constraints: allowed downtime (ideally zero or near zero for replica sets), maintenance window, data size
  • Success: measurable health checks, application smoke tests, and performance thresholds

Example success criteria:

  • All nodes on new version
  • Feature compatibility set to the new version
  • App smoke tests pass (connect, read, write, index use)
  • Errors and slow queries do not increase over baseline

2) Inventory and compatibility

Collect what you have today:

  • Topology: standalone, replica set, or sharded cluster
  • Versions: MongoDB, feature compatibility, and drivers
  • Storage and disk headroom
  • Critical ops: large collections, capped collections, TTL indexes, text and geo indexes

Useful checks in mongosh:

// Current feature compatibility version
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })

// Server build info
db.version()
db.serverBuildInfo()

Driver readiness (Node.js example):

  • Use a driver version that supports the target server
  • Enable retryable writes when appropriate
  • Set sensible timeouts and connection pool sizes

3) Backups and restore rehearsal

Take a consistent backup and prove you can restore it before touching production.

# Logical backup (portable across versions)
mongodump --uri "mongodb://user: pass@host:27017/dbname" \
  --out ./backup-$(date +%Y%m%d-%H%M)

# Restore rehearsal into a throwaway database
mongorestore --uri "mongodb://user: pass@staging:27017/rehearsal" \
  ./backup-YYYYMMDD-HHMM

Also consider filesystem snapshots if your storage platform supports them. Keep at least one verified snapshot per node.

4) Staging environment

Mirror production settings as closely as possible:

  • Same major MongoDB version as production baseline
  • Same auth mode, TLS settings, and indexes
  • Representative data volume (subset is fine)

Load seed data using mongorestore, or let the application generate it.

5) Application readiness (Node.js + Express)

Prepare the app for rolling changes and transient errors.

// app.js
const express = require('express');
const { MongoClient } = require('mongodb');

const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/appdb';

async function start() {
  const client = new MongoClient(MONGODB_URI, {
    maxPoolSize: 20,
    serverSelectionTimeoutMS: 5000,
    retryWrites: true
  });
  await client.connect();
  const db = client.db();

  // Simple readiness check using ping
  async function dbReady() {
    try {
      await db.command({ ping: 1 });
      return true;
    } catch (e) {
      return false;
    }
  }

  const app = express();
  app.get('/healthz', async (req, res) => {
    res.json({ ok: await dbReady() });
  });

  app.get('/smoke', async (req, res) => {
    const col = db.collection('items');
    await col.insertOne({ t: new Date(), v: Math.random() });
    const count = await col.countDocuments();
    res.json({ count });
  });

  const port = process.env.PORT || 3000;
  app.listen(port, () => console.log(`HTTP on ${port}`));
}

start().catch((e) => {
  console.error('Fatal:', e);
  process.exit(1);
});

Keep configuration in environment variables. Practice a short outage by restarting primaries in staging and watch the app recover.

6) Execution plan

Choose the right flow for your topology.

Single node

  1. Stop writes from the application (set maintenance mode or stop service)
  2. Backup: take a snapshot and a mongodump
  3. Stop mongod
  4. Install or switch to the new MongoDB binary or container image
  5. Start mongod and watch logs for successful startup
  6. Run basic checks and smoke tests
  7. If all good, enable writes

Replica set (rolling, near zero downtime)

  1. Ensure backups and recent snapshots for all nodes
  2. Upgrade secondaries one by one:
  • Stop a secondary
  • Install or switch to new binary/image
  • Start it and wait until it is SECONDARY and caught up
  1. Step down the primary from mongosh:
rs.stepDown(60)
  1. Upgrade the former primary using the same steps
  2. When all nodes run the new version, set feature compatibility to the new version from the primary:
db.adminCommand({ setFeatureCompatibilityVersion: "X.Y" })
  1. Run smoke tests and deeper validations

Notes:

  • Keep the application connected with retryWrites and sensible timeouts
  • Expect brief failovers; the driver should reconnect automatically

Sharded cluster (high level)

  • Upgrade config servers first, then mongos routers, then shard replica sets
  • Treat each shard as its own rolling upgrade

7) Validation checklist

Run quick checks immediately, then deeper checks within the same window.

Immediate:

  • Connect and ping
  • Read and write a small document
  • Confirm replica set is healthy: PRIMARY and SECONDARY roles as expected
rs.status()
  • Confirm expected version:
db.version()
db.serverBuildInfo().version

Deeper:

  • Run slow query and error rate comparisons vs baseline
  • Validate critical indexes exist and are used by your hot paths
// Sample index creation during migration
const col = db.getSiblingDB('appdb').items;
col.createIndex({ userId: 1, createdAt: -1 }, { name: 'ix_user_createdAt' })
  • Run application-specific smoke tests (login, order create, etc.)

8) Rollback plan

Have a clear, rehearsed path to the previous state.

  • For single node: stop writes, stop mongod, restore from snapshot or mongorestore to a fresh data directory, start the previous version binary/image, run validation
  • For replica sets: roll nodes back one by one to the previous binary/image, restoring data only if the node cannot start cleanly
  • If you changed feature compatibility, avoid using new features until the validation window ends, so rollback stays simple
  • Keep the app capable of connecting to the previous cluster version without code changes

Rollback smoke list:

  • App connects and passes /smoke
  • Data shape and key indexes match expectations
  • Primary election and replication are healthy

9) Monitoring and security

Monitoring:

  • Track connections, operation latencies, replication lag, and CPU/memory
  • Tail logs for warnings and slow queries

Security:

  • Require authentication and least-privilege roles for the app user
  • Store connection strings in secrets, not in code
  • Use TLS where appropriate and test certificate rotation in staging

Local Pilot Plan

Keep the first pilot small, fast, and easy to debug. The goal is to exercise the critical steps end to end on your laptop.

Scope:

  • Single-node MongoDB
  • Seed a tiny dataset
  • Upgrade to a new version
  • Validate with a Node.js Express app
  • Practice rollback

1) Docker Compose for a single node

docker-compose.yml:

version: '3.8'
services:
  mongo:
    image: mongo:6.0
    container_name: mongo-old
    ports:
      - '27017:27017'
    volumes:
      - ./data:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example

Start it:

docker compose up -d

Initialize a database user for the app (mongosh inside the container or from your host):

mongosh "mongodb://root: example@localhost:27017/admin" --eval 'db.getSiblingDB("appdb").createUser({ user: "app", pwd: "appsecret", roles: [ { role: "readWrite", db: "appdb" } ] })'

2) Seed data

export MONGODB_URI="mongodb://app: appsecret@localhost:27017/appdb"
mongosh "$MONGODB_URI" --eval 'db.items.insertMany([{name:"alpha"},{name:"beta"}])'

3) Run the Express app

Create app.js from the earlier snippet and run:

export MONGODB_URI="mongodb://app: appsecret@localhost:27017/appdb"
node app.js

Check health and smoke:

curl http://localhost:3000/healthz
curl http://localhost:3000/smoke

4) Backup

mongodump --uri "$MONGODB_URI" --out ./backup-preupgrade

Optionally take a filesystem snapshot of ./data.

5) Upgrade locally

Stop the old container and start a new one with the same data directory:

docker compose down

Replace the image tag to a newer version in docker-compose.yml, for example:

image: mongo:7.0

Start it:

docker compose up -d

Watch logs until you see a clean startup:

docker logs -f mongo-old

Run validation:

mongosh "$MONGODB_URI" --eval 'db.version()'

curl http://localhost:3000/healthz
curl http://localhost:3000/smoke

Optionally set feature compatibility if needed:

mongosh "mongodb://root: example@localhost:27017/admin" --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "X.Y" })'

6) Simple migration example

Create an index and backfill a field:

mongosh "$MONGODB_URI" --eval '
  db.items.createIndex({ name: 1 }, { name: "ix_name" });
  db.items.updateMany({ migrated: { $exists: false } }, { $set: { migrated: true } });
'

Verify:

mongosh "$MONGODB_URI" --eval 'db.items.getIndexes()'

7) Rollback drill

Stop the new container, restore the dump into a fresh data dir, and run the previous image.

# Stop and remove containers
docker compose down

# Move current data aside and recreate a clean directory
mv ./data ./data-new && mkdir ./data

# Restore backup into a standalone mongod
mongod --dbpath ./data --bind_ip 127.0.0.1 --port 27018 --fork --logpath ./mongod.log
mongorestore --uri "mongodb://localhost:27018/appdb" ./backup-preupgrade/appdb

# Stop the temporary mongod
mongosh "mongodb://localhost:27018/admin" --eval 'db.shutdownServer()'

# Switch image back to mongo:6.0 in docker-compose.yml and start
docker compose up -d

# Recheck app smoke tests
curl http://localhost:3000/healthz
curl http://localhost:3000/smoke

Outcome: you can upgrade, validate, and roll back locally within minutes.

Conclusion

You now have a practical path to upgrade MongoDB with low risk:

  • Separate planning, backups, staging, execution, validation, and rollback
  • Use a rolling approach for replica sets to minimize downtime
  • Validate with targeted health checks, Node.js smoke tests, and index verification
  • Keep a tested rollback using snapshots and mongodump
  • Bake in monitoring and least-privilege security from the start

Next steps:

  • Expand the local pilot into a staging replica set and repeat the drills
  • Automate smoke tests and checks in your deployment scripts
  • Document your exact cutover and rollback commands for production

Article Quality Score

Reader usefulness 100%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL