Backup Monitoring
Complete examples for monitoring backup jobs
PostgreSQL Backup
Complete script for monitoring PostgreSQL backups with error detection:
#!/bin/bash
# postgres-backup.sh
set -e
# Configuration
DB_NAME="production"
BACKUP_DIR="/backups/postgres"
MONITOR_URL="https://telemetry.host/ping/PROJECT_KEY/timeout/26h/postgres-backup?create=1"
RETENTION_DAYS=30
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Generate backup filename
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz"
# Start time
START_TIME=$(date +%s)
# Perform backup
echo "Starting PostgreSQL backup..."
if pg_dump "$DB_NAME" | gzip > "$BACKUP_FILE"; then
# Calculate duration
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
# Get backup size
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
# Clean old backups
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
REMAINING=$(find "$BACKUP_DIR" -name "*.sql.gz" | wc -l)
# Report success
curl -X POST "$MONITOR_URL" \
-H "Content-Type: application/json" \
-d "{
\"status\": \"success\",
\"message\": \"Backup completed: $SIZE in ${DURATION}s\",
\"duration\": $DURATION,
\"metadata\": {
\"file\": \"$BACKUP_FILE\",
\"size\": \"$SIZE\",
\"backups_retained\": $REMAINING
}
}"
echo "Backup completed successfully"
else
# Report failure
curl -X POST "$MONITOR_URL" \
-d '{"status":"error","message":"Backup failed"}'
echo "Backup failed!" >&2
exit 1
fi
Add to crontab:
# Daily at 2 AM
0 2 * * * /usr/local/bin/postgres-backup.sh
MySQL Backup with Verification
#!/bin/bash
# mysql-backup-verified.sh
DB_NAME="myapp"
BACKUP_DIR="/backups/mysql"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"
# Backup
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d).sql.gz"
mysqldump "$DB_NAME" | gzip > "$BACKUP_FILE"
# Verify backup integrity
if gunzip -t "$BACKUP_FILE" 2>/dev/null; then
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
curl -X POST "$MONITOR_URL" \
-d "{\"status\":\"success\",\"message\":\"Backup verified: $SIZE\"}"
else
curl -X POST "$MONITOR_URL" \
-d '{"status":"error","message":"Backup verification failed"}'
exit 1
fi
File System Backup with rsync
#!/bin/bash
# rsync-backup.sh
SOURCE="/var/www"
DEST="/mnt/backup/www"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"
# Create log file
LOG_FILE="/tmp/rsync-backup-$(date +%Y%m%d).log"
# Perform rsync
if rsync -avz --delete "$SOURCE/" "$DEST/" > "$LOG_FILE" 2>&1; then
# Extract stats
FILES=$(grep "Number of files transferred" "$LOG_FILE" | awk '{print $5}')
SIZE=$(grep "Total transferred file size" "$LOG_FILE" | awk '{print $5,$6}')
# Send log and stats
{
echo "Backup completed"
echo "Files transferred: $FILES"
echo "Total size: $SIZE"
echo ""
cat "$LOG_FILE"
} | curl -X POST "$MONITOR_URL" \
-H "Content-Type: text/plain" \
--data-binary @-
else
# Send error
cat "$LOG_FILE" | curl -X POST "$MONITOR_URL" \
-H "Content-Type: text/plain" \
--data-binary @-
exit 1
fi
rm "$LOG_FILE"
S3 Backup
#!/bin/bash
# s3-backup.sh
SOURCE_DIR="/data"
S3_BUCKET="s3://my-backups/daily"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"
# Sync to S3
OUTPUT=$(aws s3 sync "$SOURCE_DIR" "$S3_BUCKET" --delete 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
# Parse S3 output
UPLOADED=$(echo "$OUTPUT" | grep "upload:" | wc -l)
DELETED=$(echo "$OUTPUT" | grep "delete:" | wc -l)
curl -X POST "$MONITOR_URL" \
-d "{\"status\":\"success\",\"message\":\"S3 sync: $UPLOADED uploaded, $DELETED deleted\"}"
else
echo "$OUTPUT" | curl -X POST "$MONITOR_URL" \
-H "Content-Type: text/plain" \
--data-binary @-
exit 1
fi
Docker Volume Backup
#!/bin/bash
# docker-volume-backup.sh
VOLUME_NAME="postgres_data"
BACKUP_DIR="/backups/volumes"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Backup volume
BACKUP_FILE="$BACKUP_DIR/${VOLUME_NAME}_$(date +%Y%m%d).tar.gz"
docker run --rm \
-v "$VOLUME_NAME":/source:ro \
-v "$BACKUP_DIR":/backup \
alpine \
tar czf "/backup/$(basename $BACKUP_FILE)" -C /source .
if [ $? -eq 0 ]; then
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
curl -X POST "$MONITOR_URL" \
-d "{\"status\":\"success\",\"message\":\"Volume backup: $SIZE\"}"
else
curl -X POST "$MONITOR_URL" \
-d '{"status":"error","message":"Volume backup failed"}'
exit 1
fi
Comprehensive Backup Script
Full-featured backup with retry logic and notifications:
#!/bin/bash
# comprehensive-backup.sh
set -euo pipefail
# Configuration
DB_NAME="production"
BACKUP_DIR="/backups"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"
MAX_RETRIES=3
RETRY_DELAY=60
# Function to send check-in
send_checkin() {
local status=$1
local message=$2
local metadata=${3:-"{}"}
curl -s -X POST "$MONITOR_URL" \
-H "Content-Type: application/json" \
-d "{
\"status\": \"$status\",
\"message\": \"$message\",
\"metadata\": $metadata
}"
}
# Function to perform backup with retry
backup_with_retry() {
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "Backup attempt $attempt of $MAX_RETRIES..."
if pg_dump "$DB_NAME" | gzip > "$BACKUP_FILE"; then
return 0
fi
echo "Attempt $attempt failed"
attempt=$((attempt + 1))
if [ $attempt -le $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
return 1
}
# Main backup process
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz"
START_TIME=$(date +%s)
echo "Starting backup process..."
if backup_with_retry; then
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
# Verify backup
if gunzip -t "$BACKUP_FILE" 2>/dev/null; then
send_checkin "success" "Backup completed and verified" \
"{\"size\":\"$SIZE\",\"duration\":$DURATION,\"file\":\"$BACKUP_FILE\"}"
echo "Backup successful: $SIZE in ${DURATION}s"
else
send_checkin "error" "Backup verification failed"
echo "Backup verification failed!" >&2
exit 1
fi
else
send_checkin "error" "Backup failed after $MAX_RETRIES attempts"
echo "Backup failed after $MAX_RETRIES attempts!" >&2
exit 1
fi
Monitoring Backup Age
Check that backups aren’t stale:
#!/bin/bash
# check-backup-age.sh
BACKUP_DIR="/backups"
MAX_AGE_HOURS=25
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"
# Find most recent backup
LATEST_BACKUP=$(find "$BACKUP_DIR" -name "*.sql.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" ")
if [ -z "$LATEST_BACKUP" ]; then
curl -X POST "$MONITOR_URL" \
-d '{"status":"error","message":"No backups found"}'
exit 1
fi
# Check age
BACKUP_AGE_SECONDS=$(( $(date +%s) - $(stat -c %Y "$LATEST_BACKUP") ))
BACKUP_AGE_HOURS=$(( BACKUP_AGE_SECONDS / 3600 ))
if [ $BACKUP_AGE_HOURS -gt $MAX_AGE_HOURS ]; then
curl -X POST "$MONITOR_URL" \
-d "{\"status\":\"error\",\"message\":\"Latest backup is ${BACKUP_AGE_HOURS}h old\"}"
exit 1
else
curl -X POST "$MONITOR_URL" \
-d "{\"status\":\"success\",\"message\":\"Latest backup is ${BACKUP_AGE_HOURS}h old\"}"
fi
Next Steps
- Learn about Docker health checks
- See CI/CD integration
- Explore cron monitoring best practices