nuewire/backup
A Laravel package for backing up your application, giving you a simple way to capture and store backups from your project. Lightweight and easy to add to a Laravel app, it fits into existing workflows for keeping copies of your data safe.
Start by confirming what you're actually installing: nuewire/backup is a thin opinionated layer over spatie/laravel-backup. Almost everything you'll debug day-to-day lives in the spatie config and commands, so read both — the wrapper's README for the Nuewire-specific knobs (disk selection, database-only vs. database-plus-files presets) and the spatie docs for the engine underneath.
composer require nuewire/backup
php artisan vendor:publish --tag=backup-config # verify actual tag in the package
Then, before writing a single line of scheduling code, do the one thing that matters: prove a backup lands somewhere you can retrieve it.
php artisan backup:run --only-db (spatie-shaped flags; confirm against the installed version).mysqldump failed silently or exported an empty schema.backup:monitor in spatie) and confirm it reports healthy.Your first real use case is almost always the same: nightly database dump to off-box storage, monitored, with a retention window. File backups come second — they're the expensive, slow, noisy half, and you should scope them deliberately rather than toggling "back up everything" on day one.
If you already have platform-level PITR (managed RDS, Cloud SQL, Forge/Vapor snapshots), you still want this — but for logical recovery (bad migration, accidental DELETE), not for hardware failure. Frame it that way to yourself or the schedule will feel redundant and get ignored.
Two-tier cadence. This is the pattern worth copying. Run a database-only backup hourly or every few hours (--only-db), and a full database-plus-files backup nightly or weekly. Databases change constantly and are cheap to dump; user uploads change slowly and are expensive to zip. Splitting them gives you a tight RPO on the data that actually mutates without paying to re-archive 40 GB of images every hour.
// routes/console.php or app/Console/Kernel.php
Schedule::command('backup:run --only-db')
->hourly()
->onOneServer()
->withoutOverlapping(60)
->runInBackground();
Schedule::command('backup:run')
->dailyAt('02:15')
->onOneServer()
->withoutOverlapping(180)
->runInBackground();
Schedule::command('backup:clean')->dailyAt('03:30')->onOneServer();
Schedule::command('backup:monitor')->dailyAt('08:00')->onOneServer();
onOneServer() is non-negotiable if you run more than one app node — without it you get N concurrent dumps hammering the database. withoutOverlapping() with a generous expiry prevents a slow nightly full backup from stacking behind the next hourly DB run.
Isolate the backup disk. Register a dedicated filesystem disk in config/filesystems.php with its own credentials, and set it as the backup destination. Do not reuse the disk your app uses for user uploads: if that bucket gets compromised or its credentials rotate badly, your backups go with it. Separate credentials also mean the app's runtime IAM role can be write-only to backups, and only your restore tooling needs read.
Choose retention deliberately, and know who owns deletion. The package can prune its own archives by count or age. If you also set an S3 lifecycle rule on the bucket, you now have two expiry policies racing, and the one you didn't think about wins. Pick one owner — usually the package for operational backups, lifecycle rules only as a backstop against the package silently dying.
Wire failures into alerting that already exists. A backup that fails quietly for six weeks is worse than no backup, because you believe you're covered. Route the package's backup events or notification channel into the same Slack/PagerDuty/email path your app already uses, and additionally health-check backup:monitor from your uptime monitor — that combination catches both "the backup errored" and "the backup never ran at all."
Treat restore as a separate, rehearsed procedure. The package backs up; it does not restore. Write a runbook: where archives live, how to name them, how to pull the latest, how to decompress and pipe into a scratch database. Then actually execute it into a throwaway environment once a quarter. Download the latest archive to your laptop at least once — you will discover credential or lifecycle-policy problems there, not during an incident.
Version the archive in the filename. If the package lets you configure the naming pattern, embed the app name and environment. On a shared bucket across staging and production, timestamp-only names are an accident waiting to happen, and "which of these 400 files is production's Tuesday?" is a bad question to ask at 3 a.m.
Local development leverage. Point the backup config at a local disk and use it as a seed-snapshot tool: dump a scrubbed production-shaped database once, commit nothing, and restore it into your dev environment instead of re-running a 20-minute seeder. This is the highest-frequency, lowest-glory daily use of the package.
Multi-service standardization. If you run several Laravel services, publish the same config shape across all of them and centralize the retention/disk values. The value here is consistency — every service backs up the same way, to the same kind of destination, with the same alerting — not any individual cleverness.
The 1.2.0 changelog is a compare link with no enumerated changes. For a dependency whose entire job is data durability, that is not enough to justify an upgrade. Stay pinned to 1.1.1 in composer.json until someone has read the actual diff and run an upgrade plus a restore test in staging. This is the single most important line in this document.
mysqldump/pg_dump must exist on the host and be executable. The most common first-run failure is a container image without the client binary, or proc_open/exec disabled by shared-host disable_functions. Symptoms: instant "successful" backup, tiny file. Test the binary directly from the same user the queue worker runs as.
Queue vs. schedule vs. web. Long backups will blow PHP max_execution_time if triggered from a web context. Run them via the scheduler or a queued job with an explicit $timeout longer than you think you need. Also confirm which queue connection the job uses — a --only-db dump on a busy database on the sync driver will hang a request.
Special characters in DB passwords. #, @, $, and quotes in DB_PASSWORD break connection strings and can produce truncated or failed dumps. If a backup fails only in production and the dump works locally, look here first.
Config cache. Anything read via env() outside a config file returns null after php artisan config:cache. All backup settings must live in config/ and be read through config(). After any .env change, re-run config:cache and then re-test a backup — this bites people on deploy.
File backups and exclusions. Full backups zip the contents of the configured directories. Without exclusions you'll archive node_modules, storage/logs, and storage/framework/cache — bloating the archive and slowing the run while backing up nothing recoverable. Explicitly include only user-generated directories, and keep a short exclusion list for caches and logs. Also watch memory: zipping very large directories can exhaust the process.
Cleanup deletes only what the package created. It matches its own naming pattern and prefix. Hand-placed files, other tools' archives, or a changed naming pattern after an upgrade can leave orphans that accumulate forever — or, worse, a naming-pattern change can cause the cleaner to stop matching old archives entirely. After changing any naming config, audit the bucket.
Backup names embed timestamps, not "latest." Any script or runbook that pulls "the current backup" must sort by the embedded timestamp (or by the cleaner's manifest), not by lexicographic filename or S3 LastModified alone. Lexicographic sorting breaks the moment the naming pattern or timezone changes.
Timezone drift. The schedule runs in the app's configured timezone, the archive names may embed UTC, and the retention window is measured in days from those timestamps. Around DST boundaries and across multi-region infra this produces "my 30-day retention kept 29 days" surprises. Pin the app timezone explicitly.
Large database on a replicated primary. Dumps should hit a read replica or run with the appropriate consistency flags, and the dump itself is a load event. On a busy production database, an hourly full dump is a genuine performance decision, not a free one.
Never run backup:run in your test suite against real config. Point the backup disk at a Storage::fake() disk and assert the command dispatches and writes, rather than actually shelling out to mysqldump. Test the wiring (disk, schedule, event listeners), and cover the dump itself with a single slow integration test you run in CI, not on every push.
Version and PHP/Laravel compatibility. Zero stars and a single-maintainer signal means you should verify compatibility against your exact Laravel and PHP majors yourself rather than trusting the constraint, and keep a fallback plan: because it's a wrapper, dropping to spatie/laravel-backup directly and porting the config is a realistic escape hatch if the wrapper goes stale. That is the strongest argument for adopting this package at all — the sunk cost is config, not architecture.
How can I help you explore Laravel packages today?