//Technology

Automate Daily MongoDB & PostgreSQL Backups to S3 with Node.js

Automate daily MongoDB and PostgreSQL backups with Node.js, compress them, and upload to Amazon S3 for reliable, unattended protection.

6 min read
Automate Daily MongoDB & PostgreSQL Backups to S3 with Node.js

Keeping production databases safe means protecting against hardware failure, accidental deletion, and ransomware. Manual dumps are fine for occasional snapshots, but most sites need a repeatable, unattended backup routine that runs every day. In this article we’ll show how to automate daily backups for MongoDB and PostgreSQL and store the resulting archive in Amazon S3 using simple Node.js scripts.

Why Node.js?

  • Cross‑platform – The same JavaScript runs on Ubuntu, Debian, AlmaLinux, Rocky Linux, and Windows Server.
  • Rich ecosystem – Packages like aws-sdk, child_process, and node-cron let you call native dump utilities and upload files with minimal code.
  • Extensible – Add compression, encryption, or notification hooks without touching the core logic.

Prerequisites

  1. A Linux server (Ubuntu/Debian or AlmaLinux/Rocky/RHEL) with root or sudo access.
  2. MongoDB and PostgreSQL installed and running.
  3. An AWS account and an S3 bucket for backups.
  4. Node.js (v18 or later) and npm installed.

Install Required Packages

1. Install MongoDB and PostgreSQL client utilities

These command‑line tools are invoked by the Node.js script.

Ubuntu / Debian (apt)

sudo apt update
sudo apt install -y mongodb-org-tools
sudo apt install -y postgresql-client

AlmaLinux / Rocky Linux / RHEL (dnf)

sudo dnf install -y mongodb-org-tools
sudo dnf install -y postgresql

On Windows Server, download the binaries from the official sites and add them to %PATH%.

2. Set up a Node.js project

# Create a directory for the backup scripts
mkdir ~/db-backup && cd ~/db-backup

# Initialise a new npm project
npm init -y

# Install AWS SDK v3 and a cron helper
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage node-cron

Creating the Backup Script

The script performs three tasks:

  1. Run mongodump and pg_dump to generate dump files.
  2. Compress the dumps into a single .tar.gz archive.
  3. Upload the archive to an S3 bucket.

File: backup.js

const { exec } = require('child_process');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { Upload } = require('@aws-sdk/lib-storage');
const cron = require('node-cron');
const path = require('path');
const fs = require('fs');
const os = require('os');

// ---------- Configuration ----------
const config = {
  awsRegion: 'ap-south-1',
  bucketName: 'my-db-backups',
  mongo: {
    uri: 'mongodb://admin:password@localhost:27017',
    dumpPath: '/tmp/mongo-dump',
  },
  postgres: {
    host: 'localhost',
    port: 5432,
    user: 'postgres',
    password: 'password',
    database: 'mydb',
    dumpPath: '/tmp/pg-dump.sql',
  },
  retentionDays: 7,
};
// -----------------------------------

// Helper to run a shell command and return a Promise
function runCommand(command, options = {}) {
  return new Promise((resolve, reject) => {
    exec(command, options, (error, stdout, stderr) => {
      if (error) {
        reject(new Error(`${command}\n${stderr}`));
      } else {
        resolve(stdout);
      }
    });
  });
}

// 1. MongoDB dump
async function dumpMongo() {
  const dumpDir = config.mongo.dumpPath;
  if (fs.existsSync(dumpDir)) {
    fs.rmSync(dumpDir, { recursive: true, force: true });
  }
  const cmd = `mongodump --uri="${config.mongo.uri}" --out="${dumpDir}"`;
  await runCommand(cmd);
}

// 2. PostgreSQL dump
async function dumpPostgres() {
  const { host, port, user, password, database, dumpPath } = config.postgres;
  const env = { ...process.env, PGPASSWORD: password };
  const cmd = `pg_dump -h ${host} -p ${port} -U ${user} -F c -b -v -f "${dumpPath}" ${database}`;
  await runCommand(cmd, { env });
}

// 3. Create a compressed archive
function createArchive() {
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  const archiveName = `db-backup-${timestamp}.tar.gz`;
  const archivePath = path.join(os.tmpdir(), archiveName);
  const mongoDir = config.mongo.dumpPath;
  const pgFile = config.postgres.dumpPath;

  const cmd = `tar -czf "${archivePath}" -C "${mongoDir}" . -C "$(dirname "${pgFile}")" "$(basename "${pgFile}")"`;
  return runCommand(cmd).then(() => archivePath);
}

// 4. Upload to S3
async function uploadToS3(filePath) {
  const client = new S3Client({ region: config.awsRegion });
  const fileStream = fs.createReadStream(filePath);
  const upload = new Upload({
    client,
    params: {
      Bucket: config.bucketName,
      Key: path.basename(filePath),
      Body: fileStream,
    },
  });
  await upload.done();
}

// Optional: delete local files older than retentionDays
function pruneOldBackups() {
  const dir = os.tmpdir();
  const now = Date.now();
  const cutoff = now - config.retentionDays * 24 * 60 * 60 * 1000;

  fs.readdirSync(dir)
    .filter(f => f.startsWith('db-backup-') && f.endsWith('.tar.gz'))
    .forEach(f => {
      const fullPath = path.join(dir, f);
      const stats = fs.statSync(fullPath);
      if (stats.mtimeMs < cutoff) {
        fs.unlinkSync(fullPath);
      }
    });
}

// Main orchestrator
async function runBackup() {
  try {
    console.log('Starting MongoDB dump...');
    await dumpMongo();
    console.log('MongoDB dump completed.');

    console.log('Starting PostgreSQL dump...');
    await dumpPostgres();
    console.log('PostgreSQL dump completed.');

    console.log('Creating archive...');
    const archivePath = await createArchive();
    console.log(`Archive created at ${archivePath}`);

    console.log('Uploading to S3...');
    await uploadToS3(archivePath);
    console.log('Upload finished.');

    pruneOldBackups();
    console.log('Backup cycle completed successfully.');
  } catch (err) {
    console.error('Backup failed:', err);
  }
}

// Schedule: run every day at 02:30 AM server local time
cron.schedule('30 2 * * *', () => {
  console.log('Scheduled backup triggered.');
  runBackup();
});

// For manual testing, uncomment the line below:
// runBackup();

Setting Up the Scheduler

The script uses node-cron to run at 02:30 AM daily. To start it on boot, create a systemd service.

Ubuntu / Debian (systemd unit)

sudo tee /etc/systemd/system/db-backup.service > /dev/null <<'EOF'
[Unit]
Description=Daily MongoDB & PostgreSQL backup service
After=network.target

[Service]
Type=simple
User=ubuntu          # replace with the user that owns the project
WorkingDirectory=/home/ubuntu/db-backup
ExecStart=/usr/bin/node /home/ubuntu/db-backup/backup.js
Restart=on-failure
Environment=AWS_ACCESS_KEY_ID=YOUR_KEY_ID
Environment=AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable db-backup.service
sudo systemctl start db-backup.service

AlmaLinux / Rocky Linux / RHEL (systemd unit)

sudo tee /etc/systemd/system/db-backup.service > /dev/null <<'EOF'
[Unit]
Description=Daily MongoDB & PostgreSQL backup service
After=network.target

[Service]
Type=simple
User=ec2-user       # replace with appropriate user
WorkingDirectory=/home/ec2-user/db-backup
ExecStart=/usr/bin/node /home/ec2-user/db-backup/backup.js
Restart=on-failure
Environment=AWS_ACCESS_KEY_ID=YOUR_KEY_ID
Environment=AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable db-backup.service
sudo systemctl start db-backup.service

On Windows Server, create a scheduled task that runs node C:\path\to\backup.js at the desired time.

Security Considerations

  • Credentials – Use IAM roles or AWS Secrets Manager instead of hard‑coding keys. If you use environment variables, set them in the systemd unit.
  • Encryption at rest – Enable S3 server‑side encryption (SSE‑S3 or SSE‑KMS). The SDK respects the bucket’s default encryption.
  • Network security – Allow only HTTPS traffic to S3. A VPC endpoint keeps traffic inside AWS.
  • Access control – Grant the IAM principal only s3:PutObject permission on the backup bucket.

Testing and Validation

  1. Run the script manually: node backup.js. Confirm a .tar.gz file appears in /tmp and that it shows up in S3.
  2. Inspect the archive without extracting:
    tar -tzf /tmp/db-backup-2023-08-01-02-30-00.tar.gz
  3. Restore a test database to verify the dump:
    • MongoDB: mongorestore --uri="mongodb://localhost:27017" /tmp/mongo-dump
    • PostgreSQL: pg_restore -d testdb /tmp/pg-dump.sql
  4. Check system logs: journalctl -u db-backup.service -f for real‑time output.

Monitoring and Alerts (Optional)

The script logs to stdout. To receive notifications, extend it to send alerts via:

  • AWS SNS, Slack webhook, or email (e.g., nodemailer)
  • Local desktop alerts with node-notifier (development only)

Wrapping each major step in try/catch already provides clear error reporting; just forward the error message to your chosen channel.

Conclusion

Automating daily backups for MongoDB and PostgreSQL with Node.js offers a lightweight, portable solution that fits naturally into a DevOps workflow. By leveraging native dump utilities, compressing the output, and uploading to Amazon S3, you get:

  • Zero‑touch daily protection.
  • Centralised, durable storage that can be versioned or replicated.
  • Flexibility to add encryption, retention policies, or alerting without rewriting the core logic.

Deploy the script on your AtoZNode VPS or dedicated server, adjust the configuration for your environment, and you’ll have reliable backups that run automatically every day.

mongodbpostgresqlnode.jsautomated backupsamazon s3linux systemdcron schedulingdata retention

Try it on your own server

Follow along on a Cloud VPS with full root access, or read the step-by-step knowledge base guides.