docs: update backup-mailcow
This commit is contained in:
parent
f430be71e3
commit
861f3663e5
1 changed files with 870 additions and 0 deletions
870
backup-mailcow.md
Normal file
870
backup-mailcow.md
Normal file
|
|
@ -0,0 +1,870 @@
|
||||||
|
---
|
||||||
|
title: Mailcow Backup and Restore Strategy
|
||||||
|
description: Mailcow backup
|
||||||
|
published: true
|
||||||
|
date: 2026-02-11T12:40:05.470Z
|
||||||
|
tags:
|
||||||
|
editor: markdown
|
||||||
|
dateCreated: 2026-02-11T01:20:59.127Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Mailcow Backup and Recovery Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document provides comprehensive backup and recovery procedures for Mailcow email server. Since Mailcow is **not running on ZFS or BTRFS**, snapshots are not available and we rely on Mailcow's native backup script combined with Kopia for offsite storage in vaults.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Common Backup Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run a manual backup (all components)
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||||||
|
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||||||
|
|
||||||
|
# Backup with multithreading (faster)
|
||||||
|
THREADS=4 MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||||||
|
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||||||
|
|
||||||
|
# List Kopia snapshots
|
||||||
|
kopia snapshot list --tags mailcow
|
||||||
|
|
||||||
|
# View backup logs
|
||||||
|
tail -f /var/log/mailcow-backup.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Restore Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore using mailcow native script (interactive)
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
./helper-scripts/backup_and_restore.sh restore
|
||||||
|
|
||||||
|
# Restore from Kopia to new server
|
||||||
|
kopia snapshot list --tags tier1-backup
|
||||||
|
kopia restore <snapshot-id> /opt/mailcow-backups/
|
||||||
|
|
||||||
|
# Check container status after restore
|
||||||
|
docker compose ps
|
||||||
|
docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Components to Backup
|
||||||
|
|
||||||
|
### 1. Docker Compose File
|
||||||
|
- **Location**: `/opt/mailcow-dockerized/docker-compose.yml` (or your installation path)
|
||||||
|
- **Purpose**: Defines all containers, networks, and volumes
|
||||||
|
- **Importance**: Critical for recreating the exact container configuration
|
||||||
|
|
||||||
|
### 2. Configuration Files
|
||||||
|
- **Primary Config**: `/opt/mailcow-dockerized/mailcow.conf`
|
||||||
|
- **Additional Configs**:
|
||||||
|
- `/opt/mailcow-dockerized/data/conf/` (all subdirectories)
|
||||||
|
- Custom SSL certificates if not using Let's Encrypt
|
||||||
|
- Any override files (e.g., `docker-compose.override.yml`)
|
||||||
|
|
||||||
|
### 3. Database
|
||||||
|
- **MySQL/MariaDB Data**: Contains all mailbox configurations, users, domains, aliases, settings
|
||||||
|
- **Docker Volume**: `mailcowdockerized_mysql-vol`
|
||||||
|
- **Container Path**: `/var/lib/mysql`
|
||||||
|
|
||||||
|
### 4. Email Data
|
||||||
|
- **Maildir Storage**: All actual email messages
|
||||||
|
- **Docker Volume**: `mailcowdockerized_vmail-vol`
|
||||||
|
- **Container Path**: `/var/vmail`
|
||||||
|
- **Size**: Typically the largest component
|
||||||
|
|
||||||
|
### 5. Additional Important Data
|
||||||
|
- **Redis Data**: `mailcowdockerized_redis-vol` (cache and sessions)
|
||||||
|
- **Rspamd Data**: `mailcowdockerized_rspamd-vol` (spam learning)
|
||||||
|
- **Crypt Data**: `mailcowdockerized_crypt-vol` (if using mailbox encryption)
|
||||||
|
- **Postfix Queue**: `mailcowdockerized_postfix-vol` (queued/deferred mail)
|
||||||
|
|
||||||
|
## Backup Strategy
|
||||||
|
|
||||||
|
### Two-Tier Backup Approach
|
||||||
|
|
||||||
|
We use a **two-tier approach** combining Mailcow's native backup script with Kopia for offsite storage:
|
||||||
|
|
||||||
|
1. **Tier 1 (Local)**: Mailcow's `backup_and_restore.sh` script creates consistent, component-level backups
|
||||||
|
2. **Tier 2 (Offsite)**: Kopia snapshots the local backups and syncs to vaults
|
||||||
|
|
||||||
|
#### Why This Approach?
|
||||||
|
|
||||||
|
- **Best of both worlds**: Native script ensures mailcow-specific consistency, Kopia provides deduplication and offsite protection
|
||||||
|
- **Component-level restore**: Can restore individual components (just vmail, just mysql, etc.) using mailcow script
|
||||||
|
- **Disaster recovery**: Full system restore from Kopia backups on new server
|
||||||
|
- **Efficient storage**: Kopia's deduplication reduces storage needs for offsite copies
|
||||||
|
|
||||||
|
#### Backup Frequency
|
||||||
|
- **Daily**: Mailcow native backup runs at 2 AM
|
||||||
|
- **Daily**: Kopia snapshot of backups runs at 3 AM
|
||||||
|
- **Retention (Local)**: 7 days of mailcow backups (managed by script)
|
||||||
|
- **Retention (Kopia/Offsite)**: 30 daily, 12 weekly, 12 monthly
|
||||||
|
|
||||||
|
### Mailcow Native Backup Script
|
||||||
|
|
||||||
|
Mailcow includes `/opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh` which handles:
|
||||||
|
- **vmail**: Email data (mailboxes)
|
||||||
|
- **mysql**: Database (using mariabackup for consistency)
|
||||||
|
- **redis**: Redis database
|
||||||
|
- **rspamd**: Spam filter learning data
|
||||||
|
- **crypt**: Encryption data
|
||||||
|
- **postfix**: Mail queue
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Uses `mariabackup` (hot backup without stopping MySQL)
|
||||||
|
- Supports multithreading for faster backups
|
||||||
|
- Architecture-aware (handles x86/ARM differences)
|
||||||
|
- Built-in cleanup with `--delete-days` parameter
|
||||||
|
- Creates compressed archives (.tar.zst or .tar.gz)
|
||||||
|
|
||||||
|
### Setting Up Mailcow Backups
|
||||||
|
|
||||||
|
#### Step 1: Configure Backup Location
|
||||||
|
|
||||||
|
Set the backup destination via environment variable or in mailcow.conf:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Option 1: Set environment variable (preferred for automation)
|
||||||
|
export MAILCOW_BACKUP_LOCATION="/opt/mailcow-backups"
|
||||||
|
|
||||||
|
# Option 2: Add to cron job directly (shown in automated script below)
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the backup directory:
|
||||||
|
```bash
|
||||||
|
mkdir -p /opt/mailcow-backups
|
||||||
|
chown -R root:root /opt/mailcow-backups
|
||||||
|
chmod 700 /opt/mailcow-backups
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Manual Backup Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Backup all components, delete backups older than 7 days
|
||||||
|
MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||||||
|
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||||||
|
|
||||||
|
# Backup with multithreading (faster for large mailboxes)
|
||||||
|
THREADS=4 MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||||||
|
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||||||
|
|
||||||
|
# Backup specific components only
|
||||||
|
MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||||||
|
./helper-scripts/backup_and_restore.sh backup vmail mysql --delete-days 7
|
||||||
|
```
|
||||||
|
|
||||||
|
**What gets created:**
|
||||||
|
- Backup directory: `/opt/mailcow-backups/mailcow-YYYY-MM-DD-HH-MM-SS/`
|
||||||
|
- Contains: `.tar.zst` compressed archives for each component
|
||||||
|
- Plus: `mailcow.conf` copy for restore reference
|
||||||
|
|
||||||
|
#### Step 3: Automated Backup Script
|
||||||
|
|
||||||
|
Create `/opt/scripts/backup-mailcow.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Mailcow Automated Backup Script
|
||||||
|
# This creates mailcow native backups, then snapshots them with Kopia for offsite storage
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
LOG_FILE="/var/log/mailcow-backup.log"
|
||||||
|
MAILCOW_DIR="/opt/mailcow-dockerized"
|
||||||
|
BACKUP_DIR="/opt/mailcow-backups"
|
||||||
|
THREADS=4 # Adjust based on your CPU cores
|
||||||
|
KEEP_DAYS=7 # Keep local mailcow backups for 7 days
|
||||||
|
|
||||||
|
echo "[${BACKUP_DATE}] ========================================" | tee -a "$LOG_FILE"
|
||||||
|
echo "[${BACKUP_DATE}] Starting Mailcow backup process" | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
# Step 1: Run mailcow's native backup script
|
||||||
|
echo "[${BACKUP_DATE}] Running mailcow native backup..." | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
cd "$MAILCOW_DIR"
|
||||||
|
|
||||||
|
# Run the backup with multithreading
|
||||||
|
THREADS=${THREADS} MAILCOW_BACKUP_LOCATION=${BACKUP_DIR} \
|
||||||
|
./helper-scripts/backup_and_restore.sh backup all --delete-days ${KEEP_DAYS} \
|
||||||
|
2>&1 | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
BACKUP_EXIT=${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
if [ $BACKUP_EXIT -ne 0 ]; then
|
||||||
|
echo "[${BACKUP_DATE}] ERROR: Mailcow backup failed with exit code ${BACKUP_EXIT}" | tee -a "$LOG_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[${BACKUP_DATE}] Mailcow native backup completed successfully" | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
# Step 2: Create Kopia snapshot of backup directory
|
||||||
|
echo "[${BACKUP_DATE}] Creating Kopia snapshot..." | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
kopia snapshot create "${BACKUP_DIR}" \
|
||||||
|
--tags mailcow,tier1-backup \
|
||||||
|
--description "Mailcow backup ${BACKUP_DATE}" \
|
||||||
|
2>&1 | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
KOPIA_EXIT=${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
if [ $KOPIA_EXIT -ne 0 ]; then
|
||||||
|
echo "[${BACKUP_DATE}] WARNING: Kopia snapshot failed with exit code ${KOPIA_EXIT}" | tee -a "$LOG_FILE"
|
||||||
|
echo "[${BACKUP_DATE}] Local mailcow backup exists but offsite copy may be incomplete" | tee -a "$LOG_FILE"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[${BACKUP_DATE}] Kopia snapshot completed successfully" | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
# Step 3: Also backup the mailcow installation directory (configs, compose files)
|
||||||
|
echo "[${BACKUP_DATE}] Backing up mailcow installation directory..." | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
kopia snapshot create "${MAILCOW_DIR}" \
|
||||||
|
--tags mailcow,config,docker-compose \
|
||||||
|
--description "Mailcow config ${BACKUP_DATE}" \
|
||||||
|
2>&1 | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
echo "[${BACKUP_DATE}] Backup process completed successfully" | tee -a "$LOG_FILE"
|
||||||
|
echo "[${BACKUP_DATE}] ========================================" | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
# Optional: Send notification on completion
|
||||||
|
# Add your notification method here (email, webhook, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
Make it executable:
|
||||||
|
```bash
|
||||||
|
chmod +x /opt/scripts/backup-mailcow.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to crontab (daily at 2 AM):
|
||||||
|
```bash
|
||||||
|
# Edit root's crontab
|
||||||
|
crontab -e
|
||||||
|
|
||||||
|
# Add this line:
|
||||||
|
0 2 * * * /opt/scripts/backup-mailcow.sh 2>&1 | logger -t mailcow-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Offsite Backup to Vaults
|
||||||
|
|
||||||
|
After local Kopia snapshots are created, sync to your offsite vaults:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Option 1: Kopia repository sync (if using multiple Kopia repos)
|
||||||
|
kopia repository sync-to filesystem --path /mnt/vault/mailcow-backup
|
||||||
|
|
||||||
|
# Option 2: Rsync to vault
|
||||||
|
rsync -avz --delete /backup/kopia-repo/ /mnt/vault/mailcow-backup/
|
||||||
|
|
||||||
|
# Option 3: Rclone to remote vault
|
||||||
|
rclone sync /backup/kopia-repo/ vault:mailcow-backup/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recovery Procedures
|
||||||
|
|
||||||
|
### Understanding Two Recovery Methods
|
||||||
|
|
||||||
|
We have **two restore methods** depending on the scenario:
|
||||||
|
|
||||||
|
1. **Mailcow Native Restore** (Preferred): For component-level or same-server recovery
|
||||||
|
2. **Kopia Full Restore**: For complete disaster recovery to a new server
|
||||||
|
|
||||||
|
### Method 1: Mailcow Native Restore (Recommended)
|
||||||
|
|
||||||
|
Use this method when:
|
||||||
|
- Restoring on the same/similar server
|
||||||
|
- Restoring specific components (just email, just database, etc.)
|
||||||
|
- Recovering from local mailcow backups
|
||||||
|
|
||||||
|
#### Step 1: List Available Backups
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Run the restore script
|
||||||
|
./helper-scripts/backup_and_restore.sh restore
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will prompt:
|
||||||
|
```
|
||||||
|
Backup location (absolute path, starting with /): /opt/mailcow-backups
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Select Backup
|
||||||
|
|
||||||
|
The script displays available backups:
|
||||||
|
```
|
||||||
|
Found project name mailcowdockerized
|
||||||
|
[ 1 ] - /opt/mailcow-backups/mailcow-2026-02-09-02-00-14/
|
||||||
|
[ 2 ] - /opt/mailcow-backups/mailcow-2026-02-10-02-00-08/
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter the number of the backup to restore.
|
||||||
|
|
||||||
|
#### Step 3: Select Components
|
||||||
|
|
||||||
|
Choose what to restore:
|
||||||
|
```
|
||||||
|
[ 0 ] - all
|
||||||
|
[ 1 ] - Crypt data
|
||||||
|
[ 2 ] - Rspamd data
|
||||||
|
[ 3 ] - Mail directory (/var/vmail)
|
||||||
|
[ 4 ] - Redis DB
|
||||||
|
[ 5 ] - Postfix data
|
||||||
|
[ 6 ] - SQL DB
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: The script will:
|
||||||
|
- Stop mailcow containers automatically
|
||||||
|
- Restore selected components
|
||||||
|
- Handle permissions correctly
|
||||||
|
- Restart containers when done
|
||||||
|
|
||||||
|
#### Example: Restore Only Email Data
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
./helper-scripts/backup_and_restore.sh restore
|
||||||
|
|
||||||
|
# When prompted:
|
||||||
|
# - Backup location: /opt/mailcow-backups
|
||||||
|
# - Select backup: 2 (most recent)
|
||||||
|
# - Select component: 3 (Mail directory)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example: Restore Database Only
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
./helper-scripts/backup_and_restore.sh restore
|
||||||
|
|
||||||
|
# When prompted:
|
||||||
|
# - Backup location: /opt/mailcow-backups
|
||||||
|
# - Select backup: 2 (most recent)
|
||||||
|
# - Select component: 6 (SQL DB)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: For database restore, the script will modify `mailcow.conf` with the database credentials from the backup. Review the changes after restore.
|
||||||
|
|
||||||
|
### Method 2: Complete Server Rebuild (Kopia Restore)
|
||||||
|
|
||||||
|
Use this when recovering to a completely new server or when local backups are unavailable.
|
||||||
|
|
||||||
|
#### Step 1: Prepare New Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update system
|
||||||
|
apt update && apt upgrade -y
|
||||||
|
|
||||||
|
# Install Docker
|
||||||
|
curl -fsSL https://get.docker.com | sh
|
||||||
|
systemctl enable docker
|
||||||
|
systemctl start docker
|
||||||
|
|
||||||
|
# Install Docker Compose
|
||||||
|
apt install docker-compose-plugin -y
|
||||||
|
|
||||||
|
# Install Kopia
|
||||||
|
curl -s https://kopia.io/signing-key | apt-key add -
|
||||||
|
echo "deb https://packages.kopia.io/apt/ stable main" | tee /etc/apt/sources.list.d/kopia.list
|
||||||
|
apt update
|
||||||
|
apt install kopia -y
|
||||||
|
|
||||||
|
# Create directory structure
|
||||||
|
mkdir -p /opt/mailcow-dockerized
|
||||||
|
mkdir -p /opt/mailcow-backups/database
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Restore Kopia Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect to your offsite vault
|
||||||
|
# If vault is mounted:
|
||||||
|
kopia repository connect filesystem --path /mnt/vault/mailcow-backup
|
||||||
|
|
||||||
|
# If vault is remote:
|
||||||
|
kopia repository connect s3 --bucket=your-bucket --access-key=xxx --secret-access-key=xxx
|
||||||
|
|
||||||
|
# List available snapshots
|
||||||
|
kopia snapshot list --tags mailcow
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Restore Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find and restore the config snapshot
|
||||||
|
kopia snapshot list --tags config
|
||||||
|
|
||||||
|
# Restore to the Mailcow directory
|
||||||
|
kopia restore <snapshot-id> /opt/mailcow-dockerized/
|
||||||
|
|
||||||
|
# Verify critical files
|
||||||
|
ls -la /opt/mailcow-dockerized/mailcow.conf
|
||||||
|
ls -la /opt/mailcow-dockerized/docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 4: Restore Mailcow Backups Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore the entire backup directory from Kopia
|
||||||
|
kopia snapshot list --tags tier1-backup
|
||||||
|
|
||||||
|
# Restore the most recent backup
|
||||||
|
kopia restore <snapshot-id> /opt/mailcow-backups/
|
||||||
|
|
||||||
|
# Verify backups were restored
|
||||||
|
ls -la /opt/mailcow-backups/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 5: Run Mailcow Native Restore
|
||||||
|
|
||||||
|
Now use mailcow's built-in restore script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Run the restore script
|
||||||
|
./helper-scripts/backup_and_restore.sh restore
|
||||||
|
|
||||||
|
# When prompted:
|
||||||
|
# - Backup location: /opt/mailcow-backups
|
||||||
|
# - Select the most recent backup
|
||||||
|
# - Select [ 0 ] - all (to restore everything)
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
1. Stop all mailcow containers
|
||||||
|
2. Restore all components (vmail, mysql, redis, rspamd, postfix, crypt)
|
||||||
|
3. Update mailcow.conf with restored database credentials
|
||||||
|
4. Restart all containers
|
||||||
|
|
||||||
|
**Alternative: Manual Restore** (if you prefer more control)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Start containers to create volumes
|
||||||
|
docker compose up -d --no-start
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Find the most recent backup directory
|
||||||
|
LATEST_BACKUP=$(ls -td /opt/mailcow-backups/mailcow-* | head -1)
|
||||||
|
echo "Restoring from: $LATEST_BACKUP"
|
||||||
|
|
||||||
|
# Extract each component manually
|
||||||
|
cd "$LATEST_BACKUP"
|
||||||
|
|
||||||
|
# Restore vmail (email data)
|
||||||
|
docker run --rm \
|
||||||
|
-v mailcowdockerized_vmail-vol:/backup \
|
||||||
|
-v "$PWD":/restore \
|
||||||
|
debian:bookworm-slim \
|
||||||
|
tar --use-compress-program='zstd -d' -xvf /restore/backup_vmail.tar.zst
|
||||||
|
|
||||||
|
# Restore MySQL
|
||||||
|
docker run --rm \
|
||||||
|
-v mailcowdockerized_mysql-vol:/backup \
|
||||||
|
-v "$PWD":/restore \
|
||||||
|
mariadb:10.11 \
|
||||||
|
tar --use-compress-program='zstd -d' -xvf /restore/backup_mysql.tar.zst
|
||||||
|
|
||||||
|
# Restore Redis
|
||||||
|
docker run --rm \
|
||||||
|
-v mailcowdockerized_redis-vol:/backup \
|
||||||
|
-v "$PWD":/restore \
|
||||||
|
debian:bookworm-slim \
|
||||||
|
tar --use-compress-program='zstd -d' -xvf /restore/backup_redis.tar.zst
|
||||||
|
|
||||||
|
# Restore other components similarly (rspamd, postfix, crypt)
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Copy mailcow.conf from backup
|
||||||
|
cp "$LATEST_BACKUP/mailcow.conf" /opt/mailcow-dockerized/mailcow.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 6: Start and Verify Mailcow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Pull latest images (or use versions from backup if preferred)
|
||||||
|
docker compose pull
|
||||||
|
|
||||||
|
# Start all services
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Monitor logs
|
||||||
|
docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 7: Post-Restore Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check container status
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
# Test web interface
|
||||||
|
curl -I https://mail.yourdomain.com
|
||||||
|
|
||||||
|
# Check mail log
|
||||||
|
docker compose logs -f postfix-mailcow
|
||||||
|
|
||||||
|
# Verify database
|
||||||
|
docker compose exec mysql-mailcow mysql -u root -p$(grep DBROOT mailcow.conf | cut -d'=' -f2) -e "SHOW DATABASES;"
|
||||||
|
|
||||||
|
# Check email storage
|
||||||
|
docker compose exec dovecot-mailcow ls -lah /var/vmail/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 2: Restore Individual Mailbox
|
||||||
|
|
||||||
|
To restore a single user's mailbox without affecting others:
|
||||||
|
|
||||||
|
#### Option A: Using Mailcow Backups (If Available)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Temporarily mount the backup
|
||||||
|
BACKUP_DIR="/opt/mailcow-backups/mailcow-YYYY-MM-DD-HH-MM-SS"
|
||||||
|
|
||||||
|
# Extract just the vmail archive to a temporary location
|
||||||
|
mkdir -p /tmp/vmail-restore
|
||||||
|
cd "$BACKUP_DIR"
|
||||||
|
tar --use-compress-program='zstd -d' -xvf backup_vmail.tar.zst -C /tmp/vmail-restore
|
||||||
|
|
||||||
|
# Find the user's mailbox
|
||||||
|
# Structure: /tmp/vmail-restore/var/vmail/domain.com/user/
|
||||||
|
ls -la /tmp/vmail-restore/var/vmail/yourdomain.com/
|
||||||
|
|
||||||
|
# Copy specific mailbox
|
||||||
|
rsync -av /tmp/vmail-restore/var/vmail/yourdomain.com/user@domain.com/ \
|
||||||
|
/var/lib/docker/volumes/mailcowdockerized_vmail-vol/_data/yourdomain.com/user@domain.com/
|
||||||
|
|
||||||
|
# Fix permissions
|
||||||
|
docker run --rm \
|
||||||
|
-v mailcowdockerized_vmail-vol:/vmail \
|
||||||
|
debian:bookworm-slim \
|
||||||
|
chown -R 5000:5000 /vmail/yourdomain.com/user@domain.com/
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf /tmp/vmail-restore
|
||||||
|
|
||||||
|
# Restart Dovecot to recognize changes
|
||||||
|
docker compose restart dovecot-mailcow
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Using Kopia Snapshot (If Local Backups Unavailable)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mount the vmail snapshot temporarily
|
||||||
|
mkdir -p /mnt/restore
|
||||||
|
kopia mount <vmail-snapshot-id> /mnt/restore
|
||||||
|
|
||||||
|
# Find the user's mailbox
|
||||||
|
# Structure: /mnt/restore/domain.com/user/
|
||||||
|
ls -la /mnt/restore/yourdomain.com/
|
||||||
|
|
||||||
|
# Copy specific mailbox
|
||||||
|
rsync -av /mnt/restore/yourdomain.com/user@domain.com/ \
|
||||||
|
/var/lib/docker/volumes/mailcowdockerized_vmail-vol/_data/yourdomain.com/user@domain.com/
|
||||||
|
|
||||||
|
# Fix permissions
|
||||||
|
chown -R 5000:5000 /var/lib/docker/volumes/mailcowdockerized_vmail-vol/_data/yourdomain.com/user@domain.com/
|
||||||
|
|
||||||
|
# Unmount
|
||||||
|
kopia unmount /mnt/restore
|
||||||
|
|
||||||
|
# Restart Dovecot to recognize changes
|
||||||
|
docker compose restart dovecot-mailcow
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 3: Database Recovery Only
|
||||||
|
|
||||||
|
If only the database is corrupted but email data is intact:
|
||||||
|
|
||||||
|
#### Option A: Using Mailcow Native Restore (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Run the restore script
|
||||||
|
./helper-scripts/backup_and_restore.sh restore
|
||||||
|
|
||||||
|
# When prompted:
|
||||||
|
# - Backup location: /opt/mailcow-backups
|
||||||
|
# - Select the most recent backup
|
||||||
|
# - Select [ 6 ] - SQL DB (database only)
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
1. Stop mailcow
|
||||||
|
2. Restore the MySQL database from the mariabackup archive
|
||||||
|
3. Update mailcow.conf with the restored database credentials
|
||||||
|
4. Restart mailcow
|
||||||
|
|
||||||
|
#### Option B: Manual Database Restore from Kopia
|
||||||
|
|
||||||
|
If local backups are unavailable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
|
||||||
|
# Stop Mailcow
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Start only MySQL
|
||||||
|
docker compose up -d mysql-mailcow
|
||||||
|
|
||||||
|
# Wait for MySQL
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# Restore from Kopia database dump
|
||||||
|
kopia snapshot list --tags database
|
||||||
|
kopia restore <snapshot-id> /tmp/db-restore/
|
||||||
|
|
||||||
|
# Import the dump
|
||||||
|
LATEST_DUMP=$(ls -t /tmp/db-restore/mailcow_*.sql | head -1)
|
||||||
|
docker compose exec -T mysql-mailcow mysql -u root -p$(grep DBROOT mailcow.conf | cut -d'=' -f2) < "$LATEST_DUMP"
|
||||||
|
|
||||||
|
# Start all services
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 4: Configuration Recovery Only
|
||||||
|
|
||||||
|
If you only need to restore configuration files:
|
||||||
|
|
||||||
|
#### Option A: From Mailcow Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find the most recent backup
|
||||||
|
LATEST_BACKUP=$(ls -td /opt/mailcow-backups/mailcow-* | head -1)
|
||||||
|
|
||||||
|
# Stop Mailcow
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Backup current config (just in case)
|
||||||
|
cp mailcow.conf mailcow.conf.pre-restore
|
||||||
|
cp docker-compose.yml docker-compose.yml.pre-restore
|
||||||
|
|
||||||
|
# Restore mailcow.conf from backup
|
||||||
|
cp "$LATEST_BACKUP/mailcow.conf" ./mailcow.conf
|
||||||
|
|
||||||
|
# If you also need other config files from data/conf/,
|
||||||
|
# you would need to extract them from the backup archives
|
||||||
|
|
||||||
|
# Restart
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: From Kopia Snapshot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore config snapshot to temporary location
|
||||||
|
kopia restore <config-snapshot-id> /tmp/mailcow-restore/
|
||||||
|
|
||||||
|
# Stop Mailcow
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Backup current config (just in case)
|
||||||
|
cp mailcow.conf mailcow.conf.pre-restore
|
||||||
|
cp docker-compose.yml docker-compose.yml.pre-restore
|
||||||
|
|
||||||
|
# Restore specific files
|
||||||
|
cp /tmp/mailcow-restore/mailcow.conf ./
|
||||||
|
cp /tmp/mailcow-restore/docker-compose.yml ./
|
||||||
|
cp -r /tmp/mailcow-restore/data/conf/* ./data/conf/
|
||||||
|
|
||||||
|
# Restart
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification and Testing
|
||||||
|
|
||||||
|
### Regular Backup Verification
|
||||||
|
|
||||||
|
Perform monthly restore tests to ensure backups are valid:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test restore to temporary location
|
||||||
|
mkdir -p /tmp/backup-test
|
||||||
|
kopia snapshot list --tags mailcow
|
||||||
|
kopia restore <snapshot-id> /tmp/backup-test/
|
||||||
|
|
||||||
|
# Verify files exist and are readable
|
||||||
|
ls -lah /tmp/backup-test/
|
||||||
|
cat /tmp/backup-test/mailcow.conf
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf /tmp/backup-test/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Monitoring Script
|
||||||
|
|
||||||
|
Create `/opt/scripts/check-mailcow-backup.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check last backup age
|
||||||
|
LAST_BACKUP=$(kopia snapshot list --tags mailcow --json | jq -r '.[0].startTime')
|
||||||
|
LAST_BACKUP_EPOCH=$(date -d "$LAST_BACKUP" +%s)
|
||||||
|
NOW=$(date +%s)
|
||||||
|
AGE_HOURS=$(( ($NOW - $LAST_BACKUP_EPOCH) / 3600 ))
|
||||||
|
|
||||||
|
if [ $AGE_HOURS -gt 26 ]; then
|
||||||
|
echo "WARNING: Last Mailcow backup is $AGE_HOURS hours old"
|
||||||
|
# Send alert (email, Slack, etc.)
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "OK: Last backup $AGE_HOURS hours ago"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disaster Recovery Checklist
|
||||||
|
|
||||||
|
When disaster strikes, follow this checklist:
|
||||||
|
|
||||||
|
- [ ] Confirm scope of failure (server, storage, specific component)
|
||||||
|
- [ ] Gather server information (hostname, IP, DNS records)
|
||||||
|
- [ ] Access offsite backup vault
|
||||||
|
- [ ] Provision new server (if needed)
|
||||||
|
- [ ] Install Docker and dependencies
|
||||||
|
- [ ] Connect to Kopia repository
|
||||||
|
- [ ] Restore configurations first
|
||||||
|
- [ ] Restore database
|
||||||
|
- [ ] Restore email data
|
||||||
|
- [ ] Start services and verify
|
||||||
|
- [ ] Test email sending/receiving
|
||||||
|
- [ ] Verify webmail access
|
||||||
|
- [ ] Check DNS records and update if needed
|
||||||
|
- [ ] Document any issues encountered
|
||||||
|
- [ ] Update recovery procedures based on experience
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
1. **DNS**: Keep DNS records documented separately. Recovery includes updating DNS if server IP changes.
|
||||||
|
|
||||||
|
2. **SSL Certificates**: Let's Encrypt certificates are in the backup but may need renewal. Mailcow will handle this automatically.
|
||||||
|
|
||||||
|
3. **Permissions**: Docker volumes have specific UID/GID requirements:
|
||||||
|
- vmail: `5000:5000`
|
||||||
|
- mysql: `999:999`
|
||||||
|
|
||||||
|
4. **Testing**: Always test recovery procedures in a lab environment before trusting them in production.
|
||||||
|
|
||||||
|
5. **Documentation**: Keep this guide and server details in a separate location (printed copy, password manager, etc.).
|
||||||
|
|
||||||
|
6. **Retention Policy**: Review Kopia retention settings periodically to balance storage costs with recovery needs.
|
||||||
|
|
||||||
|
## Backup Architecture Notes
|
||||||
|
|
||||||
|
### Why Two Backup Layers?
|
||||||
|
|
||||||
|
**Mailcow Native Backups** (Tier 1):
|
||||||
|
- ✅ Component-aware (knows about mailcow's structure)
|
||||||
|
- ✅ Uses mariabackup for consistent MySQL hot backups
|
||||||
|
- ✅ Fast, selective restore (can restore just one component)
|
||||||
|
- ✅ Architecture-aware (handles x86/ARM differences)
|
||||||
|
- ❌ No deduplication (full copies each time)
|
||||||
|
- ❌ Limited to local storage initially
|
||||||
|
|
||||||
|
**Kopia Snapshots** (Tier 2):
|
||||||
|
- ✅ Deduplication and compression
|
||||||
|
- ✅ Efficient offsite replication to vaults
|
||||||
|
- ✅ Point-in-time recovery across multiple versions
|
||||||
|
- ✅ Disaster recovery to completely new infrastructure
|
||||||
|
- ❌ Less component-aware (treats as files)
|
||||||
|
- ❌ Slower for granular component restore
|
||||||
|
|
||||||
|
### Storage Efficiency
|
||||||
|
|
||||||
|
Using this two-tier approach:
|
||||||
|
- **Local**: Mailcow creates ~7 days of native backups (may be large, but short retention)
|
||||||
|
- **Offsite**: Kopia deduplicates these backups for long-term vault storage (much smaller)
|
||||||
|
|
||||||
|
Example storage calculation (10GB mailbox):
|
||||||
|
- Local: 7 days × 10GB = ~70GB (before compression)
|
||||||
|
- Kopia (offsite): First backup ~10GB, subsequent backups only store changes (might be <1GB/day after dedup)
|
||||||
|
|
||||||
|
### Compression Formats
|
||||||
|
|
||||||
|
Mailcow's script creates `.tar.zst` (Zstandard) or `.tar.gz` (gzip) files:
|
||||||
|
- **Zstandard** (modern): Better compression ratio, faster (recommended)
|
||||||
|
- **Gzip** (legacy): Wider compatibility with older systems
|
||||||
|
|
||||||
|
Verify your backup compression:
|
||||||
|
```bash
|
||||||
|
ls -lh /opt/mailcow-backups/mailcow-*/
|
||||||
|
# Look for .tar.zst (preferred) or .tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cross-Architecture Considerations
|
||||||
|
|
||||||
|
**Important for ARM/x86 Migration**:
|
||||||
|
|
||||||
|
Mailcow's backup script is architecture-aware. When restoring:
|
||||||
|
- **Rspamd data** cannot be restored across different architectures (x86 ↔ ARM)
|
||||||
|
- **All other components** (vmail, mysql, redis, postfix, crypt) are architecture-independent
|
||||||
|
|
||||||
|
If migrating between architectures:
|
||||||
|
```bash
|
||||||
|
# Restore everything EXCEPT rspamd
|
||||||
|
# Select components individually: vmail, mysql, redis, postfix, crypt
|
||||||
|
# Skip rspamd - it will rebuild its learning database over time
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Your Backups
|
||||||
|
|
||||||
|
**Monthly Test Protocol**:
|
||||||
|
|
||||||
|
1. **Verify local backups exist**:
|
||||||
|
```bash
|
||||||
|
ls -lh /opt/mailcow-backups/
|
||||||
|
# Should see recent dated directories
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Verify Kopia snapshots**:
|
||||||
|
```bash
|
||||||
|
kopia snapshot list --tags mailcow
|
||||||
|
# Should see recent snapshots
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test restore in lab** (recommended quarterly):
|
||||||
|
- Spin up a test VM
|
||||||
|
- Restore from Kopia
|
||||||
|
- Run mailcow native restore
|
||||||
|
- Verify email delivery and webmail access
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [Mailcow Official Backup Documentation](https://docs.mailcow.email/backup_restore/b_n_r-backup/)
|
||||||
|
- [Kopia Documentation](https://kopia.io/docs/)
|
||||||
|
- [Docker Volume Backup Best Practices](https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes)
|
||||||
|
|
||||||
|
## Revision History
|
||||||
|
|
||||||
|
| Date | Version | Changes |
|
||||||
|
|------|---------|---------|
|
||||||
|
| 2026-02-10 | 1.1 | Integrated mailcow native backup_and_restore.sh script as primary backup method |
|
||||||
|
| 2026-02-10 | 1.0 | Initial documentation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: February 10, 2026
|
||||||
|
**Maintained By**: System Administrator
|
||||||
|
**Review Schedule**: Quarterly
|
||||||
Loading…
Add table
Add a link
Reference in a new issue