Overview
Regular backups protect your Documenso data from hardware failures, software bugs, human error, and security incidents. This guide covers comprehensive backup strategies for all Documenso data.
What to Backup
A complete Documenso backup includes:
PostgreSQL Database : User accounts, documents, templates, signatures, audit logs
Document Files : PDF files (stored in database or S3)
Configuration : Environment variables, Docker configs, custom settings
Certificates : SSL certificates and signing certificates
The backup frequency depends on your usage patterns. For production systems with frequent document signing, consider automated daily backups with hourly incremental backups.
Database Backups
PostgreSQL Backup with Docker
For PostgreSQL running in Docker:
#!/bin/bash
# backup-database.sh
BACKUP_DIR = "/backups/documenso"
TIMESTAMP = $( date +%Y%m%d-%H%M%S )
CONTAINER_NAME = "documenso-db" # Adjust to your container name
DB_NAME = "documenso"
DB_USER = "documenso"
# Create backup directory
mkdir -p " $BACKUP_DIR "
# Create backup
docker exec " $CONTAINER_NAME " pg_dump -U " $DB_USER " " $DB_NAME " | \
gzip > " $BACKUP_DIR /documenso-db- $TIMESTAMP .sql.gz"
# Verify backup was created
if [ -f " $BACKUP_DIR /documenso-db- $TIMESTAMP .sql.gz" ]; then
echo "Backup created: documenso-db- $TIMESTAMP .sql.gz"
echo "Size: $( du -h $BACKUP_DIR /documenso-db- $TIMESTAMP .sql.gz | cut -f1 )"
else
echo "ERROR: Backup failed!"
exit 1
fi
# Keep only last 30 days of backups
find " $BACKUP_DIR " -name "documenso-db-*.sql.gz" -mtime +30 -delete
Make the script executable and run it:
chmod +x backup-database.sh
./backup-database.sh
PostgreSQL Backup (Bare Metal)
For PostgreSQL running directly on the host:
#!/bin/bash
# backup-database-local.sh
BACKUP_DIR = "/backups/documenso"
TIMESTAMP = $( date +%Y%m%d-%H%M%S )
DB_NAME = "documenso"
DB_USER = "documenso"
DB_HOST = "localhost"
DB_PORT = "5432"
mkdir -p " $BACKUP_DIR "
# Create backup with pg_dump
PGPASSWORD = " $DB_PASSWORD " pg_dump \
-h " $DB_HOST " \
-p " $DB_PORT " \
-U " $DB_USER " \
-d " $DB_NAME " \
--format=custom \
--file= " $BACKUP_DIR /documenso-db- $TIMESTAMP .dump"
# Compress the backup
gzip " $BACKUP_DIR /documenso-db- $TIMESTAMP .dump"
echo "Backup completed: documenso-db- $TIMESTAMP .dump.gz"
Automated Backups with Cron
Schedule automatic backups using cron:
# Edit crontab
crontab -e
# Add daily backup at 2 AM
0 2 * * * /path/to/backup-database.sh >> /var/log/documenso-backup.log 2>&1
# Or hourly backups during business hours (9 AM - 6 PM)
0 9-18 * * * /path/to/backup-database.sh >> /var/log/documenso-backup.log 2>&1
Continuous Archiving (Point-in-Time Recovery)
For mission-critical deployments, set up PostgreSQL WAL archiving:
# In postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /mnt/wal-archive/%f && cp %p /mnt/wal-archive/%f'
archive_timeout = 300 # Archive every 5 minutes
This enables point-in-time recovery to any moment between backups.
File Storage Backups
Documenso supports two storage backends for document files:
Database Storage (Default)
If using NEXT_PUBLIC_UPLOAD_TRANSPORT="database" (default), document files are stored in the DocumentData table. Your database backups include all files - no additional backup needed.
Pros : Simple, everything in one backup
Cons : Large database size, slower queries on large datasets
S3 Storage
If using NEXT_PUBLIC_UPLOAD_TRANSPORT="s3", files are stored in S3 (or S3-compatible storage like MinIO).
AWS S3 Backup
#!/bin/bash
# backup-s3.sh
BUCKET_NAME = "documenso-documents"
BACKUP_DIR = "/backups/documenso/s3"
TIMESTAMP = $( date +%Y%m%d-%H%M%S )
mkdir -p " $BACKUP_DIR "
# Sync entire bucket to local backup
aws s3 sync s3:// $BUCKET_NAME " $BACKUP_DIR / $TIMESTAMP "
# Or create a point-in-time snapshot using versioning
# (Ensure S3 versioning is enabled on the bucket)
aws s3api put-bucket-versioning \
--bucket $BUCKET_NAME \
--versioning-configuration Status=Enabled
echo "S3 backup completed: $BACKUP_DIR / $TIMESTAMP "
MinIO Backup (Self-Hosted S3)
#!/bin/bash
# backup-minio.sh
MINIO_ALIAS = "documenso-minio"
BUCKET_NAME = "documenso"
BACKUP_DIR = "/backups/documenso/minio"
TIMESTAMP = $( date +%Y%m%d-%H%M%S )
mkdir -p " $BACKUP_DIR "
# Configure MinIO client (mc) if not already done
# mc alias set $MINIO_ALIAS http://localhost:9000 $MINIO_ACCESS_KEY $MINIO_SECRET_KEY
# Mirror bucket to local backup
mc mirror $MINIO_ALIAS / $BUCKET_NAME " $BACKUP_DIR / $TIMESTAMP "
echo "MinIO backup completed: $BACKUP_DIR / $TIMESTAMP "
For S3/MinIO, enable versioning on your bucket. This provides automatic backups of every file version and protects against accidental deletion.
Configuration Backups
Backup your configuration files:
#!/bin/bash
# backup-config.sh
BACKUP_DIR = "/backups/documenso/config"
TIMESTAMP = $( date +%Y%m%d-%H%M%S )
DOCUMENSO_DIR = "/path/to/documenso"
mkdir -p " $BACKUP_DIR / $TIMESTAMP "
# Backup environment variables
cp " $DOCUMENSO_DIR /.env" " $BACKUP_DIR / $TIMESTAMP /.env"
# Backup Docker Compose configuration
cp " $DOCUMENSO_DIR /docker-compose.yml" " $BACKUP_DIR / $TIMESTAMP /docker-compose.yml"
# Backup any custom configuration files
cp -r " $DOCUMENSO_DIR /config" " $BACKUP_DIR / $TIMESTAMP /config" 2> /dev/null || true
# Create a tarball
tar -czf " $BACKUP_DIR /config- $TIMESTAMP .tar.gz" -C " $BACKUP_DIR " " $TIMESTAMP "
rm -rf " $BACKUP_DIR / $TIMESTAMP "
echo "Configuration backup: config- $TIMESTAMP .tar.gz"
Configuration files contain sensitive data (passwords, API keys, secrets). Store these backups securely with encryption and restricted access.
Certificate Backups
Backup signing and SSL certificates:
#!/bin/bash
# backup-certificates.sh
BACKUP_DIR = "/backups/documenso/certificates"
TIMESTAMP = $( date +%Y%m%d-%H%M%S )
mkdir -p " $BACKUP_DIR "
# Backup signing certificate
if [ -f "/path/to/cert.p12" ]; then
cp "/path/to/cert.p12" " $BACKUP_DIR /signing-cert- $TIMESTAMP .p12"
fi
# Backup SSL certificates (if using local certs)
if [ -d "/etc/letsencrypt/live/your-domain.com" ]; then
tar -czf " $BACKUP_DIR /ssl-certs- $TIMESTAMP .tar.gz" \
-C /etc/letsencrypt/live your-domain.com
fi
echo "Certificate backup completed"
Complete Backup Script
Combine all backups into a single script:
#!/bin/bash
# complete-backup.sh - Full Documenso backup
set -e # Exit on error
BACKUP_ROOT = "/backups/documenso"
TIMESTAMP = $( date +%Y%m%d-%H%M%S )
LOG_FILE = "/var/log/documenso-backup.log"
log () {
echo "[$( date +'%Y-%m-%d %H:%M:%S')] $1 " | tee -a " $LOG_FILE "
}
log "Starting Documenso backup..."
# 1. Backup database
log "Backing up database..."
mkdir -p " $BACKUP_ROOT /database"
docker exec documenso-db pg_dump -U documenso documenso | \
gzip > " $BACKUP_ROOT /database/documenso-db- $TIMESTAMP .sql.gz"
# 2. Backup S3 files (if applicable)
if grep -q 'NEXT_PUBLIC_UPLOAD_TRANSPORT="s3"' .env ; then
log "Backing up S3 storage..."
mkdir -p " $BACKUP_ROOT /s3"
aws s3 sync s3://documenso-bucket " $BACKUP_ROOT /s3/ $TIMESTAMP "
fi
# 3. Backup configuration
log "Backing up configuration..."
mkdir -p " $BACKUP_ROOT /config"
tar -czf " $BACKUP_ROOT /config/config- $TIMESTAMP .tar.gz" \
.env docker-compose.yml
# 4. Backup certificates
log "Backing up certificates..."
mkdir -p " $BACKUP_ROOT /certificates"
cp /path/to/cert.p12 " $BACKUP_ROOT /certificates/cert- $TIMESTAMP .p12" 2> /dev/null || true
# 5. Create manifest file
log "Creating backup manifest..."
cat > " $BACKUP_ROOT /manifest- $TIMESTAMP .txt" << EOF
Documenso Backup Manifest
Created: $( date )
Version: $( docker exec documenso cat package.json | grep version | head -1 | cut -d '"' -f4 )
Files:
$( ls -lh $BACKUP_ROOT /database/documenso-db- $TIMESTAMP .sql.gz)
$( ls -lh $BACKUP_ROOT /config/config- $TIMESTAMP .tar.gz)
EOF
# 6. Cleanup old backups (keep last 30 days)
log "Cleaning up old backups..."
find " $BACKUP_ROOT " -type f -mtime +30 -delete
log "Backup completed successfully!"
log "Location: $BACKUP_ROOT "
Restore Procedures
Restore Database
Restore a database backup:
#!/bin/bash
# restore-database.sh
BACKUP_FILE = " $1 " # Path to backup file
if [ -z " $BACKUP_FILE " ]; then
echo "Usage: $0 <backup-file.sql.gz>"
exit 1
fi
read -p "⚠️ This will OVERWRITE the current database. Continue? (yes/no): " confirm
if [ " $confirm " != "yes" ]; then
echo "Restore cancelled."
exit 0
fi
# Stop Documenso to prevent connections
docker compose stop documenso
# Drop and recreate database
docker exec documenso-db psql -U documenso -c "DROP DATABASE IF EXISTS documenso;"
docker exec documenso-db psql -U documenso -c "CREATE DATABASE documenso;"
# Restore from backup
gunzip -c " $BACKUP_FILE " | docker exec -i documenso-db psql -U documenso -d documenso
# Start Documenso
docker compose start documenso
echo "✅ Database restored successfully!"
echo "🔍 Verify at: http://localhost:3000/api/health"
Restore S3 Files
#!/bin/bash
# restore-s3.sh
BACKUP_DIR = " $1 " # Directory containing S3 backup
BUCKET_NAME = "documenso-documents"
if [ -z " $BACKUP_DIR " ]; then
echo "Usage: $0 <backup-directory>"
exit 1
fi
read -p "⚠️ This will overwrite current S3 files. Continue? (yes/no): " confirm
if [ " $confirm " != "yes" ]; then
echo "Restore cancelled."
exit 0
fi
# Sync backup to S3
aws s3 sync " $BACKUP_DIR " s3:// $BUCKET_NAME --delete
echo "✅ S3 files restored successfully!"
Restore Configuration
# Extract configuration backup
tar -xzf config-20260304-120000.tar.gz
# Copy files to Documenso directory
cp .env /path/to/documenso/.env
cp docker-compose.yml /path/to/documenso/docker-compose.yml
# Restart to apply configuration
docker compose restart
Complete Restore
Full disaster recovery from backups:
Prepare clean environment
# Stop any running Documenso containers
docker compose down
# Clean volumes (optional - only if doing full restore)
docker volume rm documenso-db-data
Restore configuration
tar -xzf config-20260304-120000.tar.gz
cp .env /path/to/documenso/.env
cp docker-compose.yml /path/to/documenso/docker-compose.yml
Start database container
docker compose up -d documenso-db
# Wait for database to be ready
sleep 10
Restore database
gunzip -c database/documenso-db-20260304-120000.sql.gz | \
docker exec -i documenso-db psql -U documenso -d documenso
Restore S3 files (if applicable)
aws s3 sync s3-backup/20260304-120000 s3://documenso-bucket
Restore certificates
cp certificates/cert-20260304-120000.p12 /path/to/cert.p12
# Update NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH in .env if needed
Start Documenso
docker compose up -d documenso
# Check logs
docker compose logs -f documenso
# Verify health
curl http://localhost:3000/api/health
Verify functionality
Login with an existing account
Open and view existing documents
Test signing a document
Check API endpoints
Off-Site Backups
Store backups in multiple locations for disaster recovery:
Cloud Storage
#!/bin/bash
# sync-backups-to-cloud.sh
BACKUP_DIR = "/backups/documenso"
# AWS S3
aws s3 sync " $BACKUP_DIR " s3://company-backups/documenso/
# Google Cloud Storage
# gsutil rsync -r "$BACKUP_DIR" gs://company-backups/documenso/
# Azure Blob Storage
# az storage blob upload-batch -d documenso-backups -s "$BACKUP_DIR"
echo "Backups synced to cloud storage"
Remote Server (rsync)
#!/bin/bash
# sync-backups-remote.sh
BACKUP_DIR = "/backups/documenso"
REMOTE_USER = "backup-user"
REMOTE_HOST = "backup-server.example.com"
REMOTE_DIR = "/backup-storage/documenso"
# Sync via rsync over SSH
rsync -avz --delete \
-e "ssh -i /root/.ssh/backup_key" \
" $BACKUP_DIR /" \
" $REMOTE_USER @ $REMOTE_HOST : $REMOTE_DIR /"
echo "Backups synced to remote server"
Backup Encryption
Encrypt backups before storing:
#!/bin/bash
# encrypt-backup.sh
BACKUP_FILE = " $1 "
ENCRYPTION_KEY = "/secure/path/backup-encryption.key"
# Encrypt with GPG
gpg --symmetric --cipher-algo AES256 --output " $BACKUP_FILE .gpg" " $BACKUP_FILE "
# Or use OpenSSL
openssl enc -aes-256-cbc -salt -pbkdf2 \
-in " $BACKUP_FILE " \
-out " $BACKUP_FILE .enc" \
-pass file:" $ENCRYPTION_KEY "
# Remove unencrypted backup
rm " $BACKUP_FILE "
echo "Backup encrypted: $BACKUP_FILE .enc"
Decrypt when restoring:
# Decrypt with GPG
gpg --decrypt backup.sql.gz.gpg > backup.sql.gz
# Or with OpenSSL
openssl enc -aes-256-cbc -d -pbkdf2 \
-in backup.sql.gz.enc \
-out backup.sql.gz \
-pass file:/secure/path/backup-encryption.key
Backup Testing
Regularly test your backups to ensure they work:
Schedule quarterly restore tests
Test full restore in a staging environment every 3 months
Verify backup integrity
# Test database backup
gunzip -t backup.sql.gz
# Test tarball
tar -tzf config-backup.tar.gz > /dev/null
Document restore procedures
Keep step-by-step restore documentation accessible offline
Test disaster recovery time
Measure how long a complete restore takes and plan accordingly
Backup Best Practices
3 copies of your data (production + 2 backups)
2 different media types (e.g., disk + cloud)
1 off-site backup (different physical location)
Manual backups are unreliable. Use cron, systemd timers, or cloud backup services to automate:
Database backups
File backups
Configuration backups
Off-site sync
Set up alerts for:
Failed backups
Backup size anomalies (too large or too small)
Missing backups (scheduled job didn’t run)
Disk space on backup storage
Implement retention policies
Balance storage costs with recovery needs:
Hourly : Keep last 24 hours
Daily : Keep last 30 days
Weekly : Keep last 12 weeks
Monthly : Keep last 12 months
Yearly : Keep indefinitely (if required for compliance)
Document and label backups
Each backup should include metadata:
Timestamp
Documenso version
Database schema version
Backup type (full, incremental)
File checksums (for integrity verification)
Backup Storage Requirements
Estimate storage needs based on your usage:
Documents/Month Database Size S3 Storage Monthly Backup Growth 100 ~500 MB ~2 GB ~3 GB 1,000 ~2 GB ~20 GB ~25 GB 10,000 ~15 GB ~200 GB ~250 GB
Database backups compress well (typically 5-10x compression). S3 files (PDFs) don’t compress much since PDFs are already compressed.
Updates Learn how to safely update Documenso
Monitoring Set up monitoring and alerting
Troubleshooting Common backup and restore issues
Database Configuration PostgreSQL setup and optimization