dgtlss/capsule
Capsule is a Laravel package for organizing your application into self-contained modules. It provides a clean structure for routes, migrations, views, and classes, making large codebases easier to navigate, reuse, and maintain.
Capsule 3.0.0 is a Laravel backup package built around artisan commands and a published config file. The upgrade is significant: it drops Laravel 10/11 and PHP 8.1, so confirm your matrix before installing.
1. Check the platform first — this is the most common reason an install fails on v3:
php -v # 8.2+ for Laravel 12, 8.3+ for Laravel 13
php -m | grep -E 'zip|sodium|zlib|pdo_sqlite|sqlite3'
zip, sodium and zlib are required. pdo_sqlite is needed to back up and validate SQLite databases; sqlite3 is additionally needed to restore them. AES-encrypted ZIP support depends on the underlying libzip build — verify with a throwaway encrypted ZIP if you rely on that format.
2. Install and publish:
composer require dgtlss/capsule
php artisan vendor:publish --tag=capsule-config
3. Configure a destination and a password. Put the password in your secret manager or environment, not in config/capsule.php:
// config/capsule.php (shape will vary — read the published file)
'destination' => [
'disk' => 's3-backups',
'path' => 'capsule',
],
'encryption' => [
'enabled' => true,
'password' => env('CAPSULE_PASSWORD'),
],
4. Run your first backup and, more importantly, your first restore. The single habit that matters with this package is proving recovery works before you need it:
php artisan backup:run --no-local --encrypt
php artisan backup:list
php artisan backup:restore --list
php artisan backup:restore --dry-run
Where to look first: the published config for destination/retention/encryption keys, php artisan backup:run --help for the option set on your exact version, and the README section on encrypted streaming recovery. Treat the release notes' "encrypted streaming recovery" block as required reading — .capsule.enc files are not ZIPs and cannot be opened by ordinary archive tools.
First real use case: a nightly encrypted streaming backup to object storage, paired with a weekly scheduled restore into a scratch database.
Scheduled encrypted streaming backup. Register the schedule and keep the local footprint small:
// routes/console.php (Laravel 11+) or app/Console/Kernel.php
Schedule::command('backup:run --no-local --encrypt')
->dailyAt('02:30')
->withoutOverlapping()
->onFailure(function () {
// alert
});
Schedule::command('backup:clean')
->dailyAt('04:00');
--no-local avoids writing a complete backup archive on the application server, which is the main operational win for large datasets. --encrypt encrypts contents, filenames, manifests and temporary remote chunks.
Keep full and incremental archives together. Incrementals are now checksum-bound to a base archive and record deletions. If you split fulls and incrementals across different disks, prefixes or retention buckets, you break the chain. Use one prefix per backup set and let cleanup run against that prefix.
Restore workflow, in order. Never restore blind:
backup:restore --list — see what is actually in the archive, including the preserved root directory layout.backup:restore --dry-run — exercise path resolution and validation without touching the destination.New-format restores preserve named or numbered file-root directories. If your archive was taken of /var/www/app, expect the restored tree to carry that root rather than flattening — inspect with --list and plan your rsync/move step accordingly.
Disaster recovery with standalone decryption. vendor/bin/capsule-decrypt recovers an encrypted streaming archive without the original Laravel application or backup catalog, which makes it viable for a break-glass runbook:
vendor/bin/capsule-decrypt backup.capsule.enc --output backup.zip
unzip -l backup.zip # inspect before extracting
Store the password and a compatible Capsule installation (or a container image containing it) separately from the application it backs up. If your app host, its secrets and its backup tooling all live in the same account, you have no recovery path for the failure mode that matters most.
Handle the decrypted ZIP as sensitive. Decryption produces plaintext. Decrypt into a temp directory with restrictive permissions, extract, then shred the ZIP. Do not leave decrypted archives on shared build agents or in CI artifacts.
Monitor by exit code and inventory, not vibes. Alert on non-zero exit from backup:run, and separately on "no archive newer than N hours" from backup:list. A silent scheduler and a successful scheduler look identical until you check inventory.
Exercise the pipeline in CI. A cheap job that runs backup:run --no-local --encrypt against a seeded SQLite database, then capsule-decrypt and backup:restore --dry-run, catches missing extensions, libzip AES gaps and manifest regressions before they reach production.
sodium and a production container that doesn't will only reveal itself at backup time. Assert required extensions in your deploy healthcheck..capsule.enc is not a ZIP. Zip tools, archive managers and naive integrity checks will fail on it. Any runbook or monitoring script that assumes ZIP must branch on the extension.--no-local is not zero-disk. It avoids a complete local backup archive, but SQLite snapshots and filesystem adapters still use temporary storage. Size /tmp and any configured temp path accordingly, or large databases will fail mid-run.--list and --dry-run first.backup:list to check the manifest is readable, and try capsule-decrypt in isolation to separate decryption failures from restore-placement failures.How can I help you explore Laravel packages today?