Skip to main content

Overview

Nguhöe EHR uses Spatie Laravel Backup to create automated backups of your database and application files. The package provides a robust solution for creating, storing, and managing backups with monitoring and notification capabilities.

Configuration File

Backup configuration is located at config/backup.php.

Basic Configuration

Application Name

backup.name
string
default:"env('APP_NAME')"
The name identifier for your backups. Used for monitoring and organization.
'name' => env('APP_NAME', 'laravel-backup'),

Source Configuration

File Backups

backup.source.files.include
array
Directories and files to include in the backup.
'include' => [
    base_path(),  // Entire application
],
backup.source.files.exclude
array
Directories and files to exclude from the backup.
'exclude' => [
    base_path('vendor'),        // Dependencies (can be reinstalled)
    base_path('node_modules'),  // Node dependencies
    storage_path('framework'),  // Cache and temporary files
],
Whether to follow symbolic links during backup.
'follow_links' => false,
backup.source.files.ignore_unreadable_directories
boolean
default:"false"
Skip directories that cannot be read instead of failing.
'ignore_unreadable_directories' => false,
backup.source.files.relative_path
string|null
default:"null"
Make paths relative to this directory in the zip file. Set to base_path() for cleaner archives.
'relative_path' => null,

Database Backups

backup.source.databases
array
Database connections to backup. Uses connection names from config/database.php.
'databases' => [
    env('DB_CONNECTION', 'mysql'),
],
Supported Databases:
  • MySQL
  • MariaDB
  • PostgreSQL
  • SQLite
  • MongoDB

Database Dump Options

Customize database dumps by adding a dump key to your database connection in config/database.php:
'mysql' => [
    // ... connection settings
    'dump' => [
        // Exclude specific tables
        'exclude_tables' => [
            'sessions',
            'cache',
            'jobs',
        ],
        
        // Use single transaction (InnoDB only - prevents table locking)
        'useSingleTransaction' => true,
        
        // Add extra mysqldump options
        'dump_command_options' => [
            '--skip-lock-tables',
        ],
    ],
],
backup.database_dump_compressor
string|null
default:"null"
Compress database dumps to save space.Options:
  • Spatie\DbDumper\Compressors\GzipCompressor::class - Gzip compression
  • null - No compression
'database_dump_compressor' => null,
backup.database_dump_file_timestamp_format
string|null
default:"null"
Add timestamp to database dump filename (e.g., ‘Y-m-d-H-i-s’).
'database_dump_file_timestamp_format' => null,
backup.database_dump_filename_base
string
default:"database"
Base name for database dump files.Options:
  • database - Use database name
  • connection - Use connection name
'database_dump_filename_base' => 'database',

Destination Configuration

backup.destination.compression_method
int
default:"ZipArchive::CM_DEFAULT"
Compression algorithm for the backup archive.Options:
  • ZipArchive::CM_STORE - No compression (0)
  • ZipArchive::CM_DEFAULT - Default compression
  • ZipArchive::CM_DEFLATE - Deflate compression
  • ZipArchive::CM_BZIP2 - Bzip2 compression
  • ZipArchive::CM_XZ - XZ compression
'compression_method' => ZipArchive::CM_DEFAULT,
backup.destination.compression_level
int
default:"9"
Compression level from 0-9. Higher values mean better compression but slower speed.
'compression_level' => 9,
backup.destination.filename_prefix
string
default:""
Prefix for backup filenames.
'filename_prefix' => '',
backup.destination.disks
array
Storage disks where backups will be stored. Must be defined in config/filesystems.php.
'disks' => [
    'local',
],

Storage Disk Examples

Local Storage:
'disks' => ['local'],
Amazon S3:
'disks' => ['s3'],
Configure S3 in config/filesystems.php and .env:
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=nguhoe-ehr-backups
Multiple Disks:
'disks' => ['local', 's3'],  // Store on both local and S3
backup.destination.continue_on_failure
boolean
default:"false"
Continue backup process even if some storage targets fail.
'continue_on_failure' => false,

Security Configuration

backup.password
string|null
default:"env('BACKUP_ARCHIVE_PASSWORD')"
Password-protect backup archives. Set in .env file.
'password' => env('BACKUP_ARCHIVE_PASSWORD'),
In .env:
BACKUP_ARCHIVE_PASSWORD=your_secure_password
backup.encryption
string
default:"default"
Encryption algorithm for password-protected archives.Options:
  • none - No encryption
  • default - AES-256 if available
  • aes128 - AES-128
  • aes192 - AES-192
  • aes256 - AES-256
'encryption' => 'default',
backup.verify_backup
boolean
default:"false"
Verify backup archive can be opened and contains files after creation. Recommended for critical backups.
'verify_backup' => false,
backup.temporary_directory
string
default:"storage_path('app/backup-temp')"
Directory for temporary files during backup creation.
'temporary_directory' => storage_path('app/backup-temp'),

Cleanup Configuration

cleanup.strategy
string
Strategy for automatically cleaning up old backups.
'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,

Default Cleanup Strategy

The default strategy keeps:
  • All backups for a certain number of days
  • Daily backups for a period after that
  • Weekly backups for a period after that
  • Monthly backups for a period after that
  • Yearly backups for a period after that
cleanup.default_strategy.keep_all_backups_for_days
int
default:"7"
Keep all backups for this many days.
'keep_all_backups_for_days' => 7,
cleanup.default_strategy.keep_daily_backups_for_days
int
default:"16"
After the initial period, keep one backup per day for this many days.
'keep_daily_backups_for_days' => 16,
cleanup.default_strategy.keep_weekly_backups_for_weeks
int
default:"8"
After the daily period, keep one backup per week for this many weeks.
'keep_weekly_backups_for_weeks' => 8,
cleanup.default_strategy.keep_monthly_backups_for_months
int
default:"4"
After the weekly period, keep one backup per month for this many months.
'keep_monthly_backups_for_months' => 4,
cleanup.default_strategy.keep_yearly_backups_for_years
int
default:"2"
After the monthly period, keep one backup per year for this many years.
'keep_yearly_backups_for_years' => 2,
cleanup.default_strategy.delete_oldest_backups_when_using_more_megabytes_than
int|null
default:"5000"
Delete oldest backups when total storage exceeds this size in megabytes. Set to null for unlimited.
'delete_oldest_backups_when_using_more_megabytes_than' => 5000,  // 5GB

Monitoring Configuration

monitor_backups
array
Configure health checks for your backups.
'monitor_backups' => [
    [
        'name' => env('APP_NAME', 'laravel-backup'),
        'disks' => ['local'],
        'health_checks' => [
            \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
            \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
        ],
    ],
],

Health Checks

MaximumAgeInDays: Alerts if the newest backup is older than specified days. MaximumStorageInMegabytes: Alerts if backups use more than specified storage.

Notification Configuration

notifications.notifications
array
Configure which events trigger notifications and through which channels.
'notifications' => [
    \Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class => ['mail'],
    \Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['mail'],
    \Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification::class => ['mail'],
    \Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class => ['mail'],
    \Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification::class => ['mail'],
    \Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification::class => ['mail'],
],

Mail Notifications

notifications.mail.to
string
Email address to receive backup notifications.
'mail' => [
    'to' => '[email protected]',
],

Slack Notifications

'slack' => [
    'webhook_url' => env('BACKUP_SLACK_WEBHOOK'),
    'channel' => '#backups',
    'username' => 'Backup Bot',
],

Discord Notifications

'discord' => [
    'webhook_url' => env('BACKUP_DISCORD_WEBHOOK'),
    'username' => 'Backup Bot',
],

Running Backups

Manual Backup

Create a backup manually:
php artisan backup:run
Options:
  • --only-db - Backup only the database
  • --only-files - Backup only files
  • --disable-notifications - Don’t send notifications

Scheduled Backups

Add to routes/console.php or create a scheduled task:
use Illuminate\Support\Facades\Schedule;

Schedule::command('backup:run')->daily()->at('02:00');
Common Schedules:
// Daily at 2 AM
Schedule::command('backup:run')->daily()->at('02:00');

// Every 6 hours
Schedule::command('backup:run')->everySixHours();

// Twice daily (2 AM and 2 PM)
Schedule::command('backup:run')->twiceDaily(2, 14);

// Weekly on Sunday at 3 AM
Schedule::command('backup:run')->weekly()->sundays()->at('03:00');

// Database only - hourly
Schedule::command('backup:run --only-db')->hourly();
Ensure the Laravel scheduler is running:
# Add to crontab
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

Cleanup Old Backups

php artisan backup:clean
Runs automatically when creating backups, but can be run manually.

Monitor Backup Health

php artisan backup:monitor
Check if backups meet health requirements.

List Backups

php artisan backup:list
Display all backups with their size and age.

Restoration Procedures

Restoring Files

  1. Download backup archive from storage
  2. Extract the archive:
    unzip backup-name.zip -d /restore/location
    
  3. If password-protected:
    unzip -P your_password backup-name.zip -d /restore/location
    
  4. Copy files to application directory

Restoring Database

MySQL/MariaDB:
  1. Extract database dump from backup archive
  2. Restore database:
    mysql -u username -p database_name < database-dump.sql
    
PostgreSQL:
psql -U username -d database_name < database-dump.sql
SQLite:
cp database-dump.sqlite /path/to/database/database.sqlite

Full Application Restoration

# 1. Extract backup
unzip backup-2024-03-04-02-00-00.zip -d /restore/temp

# 2. Restore files
cp -r /restore/temp/* /var/www/ehr

# 3. Restore database
mysql -u nguhoe_user -p nguhoe_ehr < /restore/temp/db-dumps/mysql-nguhoe_ehr.sql

# 4. Set permissions
chown -R www-data:www-data /var/www/ehr
chmod -R 755 /var/www/ehr/storage
chmod -R 755 /var/www/ehr/bootstrap/cache

# 5. Clear and rebuild cache
php artisan config:cache
php artisan route:cache
php artisan view:cache

Best Practices

Production Configuration

'backup' => [
    'name' => env('APP_NAME'),
    
    'source' => [
        'files' => [
            'include' => [base_path()],
            'exclude' => [
                base_path('vendor'),
                base_path('node_modules'),
                storage_path('framework/cache'),
                storage_path('framework/sessions'),
                storage_path('framework/views'),
            ],
        ],
        'databases' => [env('DB_CONNECTION')],
    ],
    
    'destination' => [
        'disks' => ['local', 's3'],  // Store locally and offsite
    ],
    
    'password' => env('BACKUP_ARCHIVE_PASSWORD'),
    'encryption' => 'default',
    'verify_backup' => true,  // Verify critical backups
],

'monitor_backups' => [
    [
        'name' => env('APP_NAME'),
        'disks' => ['s3'],  // Monitor offsite backups
        'health_checks' => [
            MaximumAgeInDays::class => 1,        // Alert if no backup in 24h
            MaximumStorageInMegabytes::class => 10000,  // Alert if > 10GB
        ],
    ],
],

Security Recommendations

  1. Always password-protect production backups
    BACKUP_ARCHIVE_PASSWORD=strong_random_password
    
  2. Store backups offsite (S3, Dropbox, etc.)
    'disks' => ['local', 's3'],
    
  3. Exclude sensitive development files
    'exclude' => [
        base_path('.env'),  // Don't backup .env directly
        base_path('node_modules'),
        base_path('vendor'),
    ],
    
  4. Monitor backup health
    Schedule::command('backup:monitor')->daily();
    
  5. Test restoration regularly

Storage Recommendations

  • Local: Quick access, but vulnerable to server failure
  • S3: Reliable offsite storage, versioning support
  • Multiple disks: Best practice - local for quick restore + offsite for disaster recovery

Scheduling Recommendations

Based on data criticality:
  • High criticality (patient data): Every 6 hours + full daily backup
  • Medium criticality: Daily backups
  • Low criticality: Weekly backups
// High criticality schedule
Schedule::command('backup:run --only-db')->everySixHours();
Schedule::command('backup:run')->daily()->at('02:00');
Schedule::command('backup:monitor')->daily()->at('03:00');
Schedule::command('backup:clean')->daily()->at('04:00');

Troubleshooting

Backup Fails with Timeout

Increase PHP timeout in backup command:
php -d max_execution_time=600 artisan backup:run

Large Backups

Exclude unnecessary files:
'exclude' => [
    base_path('storage/logs'),
    storage_path('app/public/temp'),
],
Or backup database separately:
php artisan backup:run --only-db

Permission Errors

Ensure storage directory is writable:
chmod -R 775 storage
chown -R www-data:www-data storage

S3 Upload Fails

Verify AWS credentials in .env:
AWS_ACCESS_KEY_ID=your_key
AWS_SECRET_ACCESS_KEY=your_secret
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket

Missing mysqldump Command

Install MySQL client:
# Ubuntu/Debian
sudo apt-get install mysql-client

# CentOS/RHEL
sudo yum install mysql

Build docs developers (and LLMs) love