Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Vanguard Laravel Package

softartisan/laravel-vanguard

Laravel Vanguard gives your Laravel application a security-first foundation, bundling opinionated defaults and helpers for authentication, authorization, and safer request handling so you can ship robust features faster.

View on GitHub
Deep Wiki
Context7

Getting Started

Vanguard is a Laravel backup/restore package built around a landlord (central) database plus per-tenant databases and files, with a health payload, a dashboard, and console commands. 2.7.0 is additive: every new behaviour is opt-in, the defaults reproduce 2.6.1 exactly, and no migration or config republish is required.

Minimal path from zero:

  1. Install the package and run php artisan vanguard:install. Its output is the fastest orientation: it names the missing env variables, the missing Flysystem adapter (if you want R2), and reports drift between your published config/vanguard.php and the shipped config.
  2. Read the drift report carefully on an existing app. It splits keys into New optional settings (disabled by default, nothing to do), which you can ignore, and missing, which are keys whose absence silently changes behaviour. notifications.mail.to is the canonical example — fix those first.
  3. Set notifications.mail.to, then run a single landlord backup and open the health screen. Confirm four things: archive extension is still .tar (unless you opted into encryption), the checksum column is populated, the tenant list renders, and the dashboard is reachable behind auth.
  4. First real use case: a scheduled landlord backup on a multi-tenant app. Everything else — per-tenant backups, RLS-safe dumps, encrypted archives, R2 destinations — is an increment on top of that working baseline.

If you are running PostgreSQL with Row-Level Security, your first use case should instead be the dedicated backup connection (below), because a dump taken as the application role is silently partial. That is the one thing in this package you should not defer.

Implementation Patterns

Keep the defaults and upgrade blindly. 2.7.0 has no breaking change for applications. Do not republish the config to "get the new settings" — nested keys fall back to 2.6 behaviour, and republishing risks clobbering your customisations. Opt in key by key instead.

Dedicated backup/restore connections (PostgreSQL + RLS). Declare a second connection pointing at the same database but with a role that can see everything (BYPASSRLS or table owner), then:

VANGUARD_BACKUP_CONNECTION=pgsql_backup
VANGUARD_RESTORE_CONNECTION=pgsql_restore

Treat the loud failure as a feature: an undeclared connection name or a driver mismatch aborts rather than falling back to the application connection. That makes it safe to assert in CI that a backup taken under the backup role contains rows the app role cannot see. For restore, use a separate connection from backup so you can keep the restore role narrower.

Single-database tenancy. For apps that keep all tenants in one shared database:

VANGUARD_TENANCY_DATABASE_MODE=single

Mental model after switching: the database is dumped once, by the landlord backup; a tenant backup contains files only; no tenant DB connection is read at all. Consequences you should design around:

  • Every tenant that should be restorable needs restore_storage configured, otherwise the console command, the restore endpoint (400) and the restore job all refuse it — by design, since there would be nothing to restore.
  • Do not promise tenants row-level restore. Extracting one tenant's rows from the shared dump is explicitly out of scope; if that is a requirement, keep multi.

Encrypted archives (age). Opt in with recipients as public keys:

VANGUARD_ENCRYPTION_ENABLED=true
VANGUARD_ENCRYPTION_RECIPIENTS=age1abc...,age1def...
# or: VANGUARD_ENCRYPTION_RECIPIENTS_FILE=/etc/vanguard/recipients.txt
VANGUARD_AGE_BINARY=/usr/local/bin/age

Archives become {name}.tar.age and are tagged encrypted on the dashboard. The private key is never persisted, so restores are console-only:

php artisan vanguard:restore {backup} --identity=/secure/age.key
php artisan vanguard:restore {backup} --identity-stdin   # for CI/KMS piping

This is the pattern that matters operationally: your scheduled backup job needs only public material, while restore is an interactive/on-call action. Wire the identity into your secret manager and into an incident runbook, not into .env.

pg_dump / server major check. Use warn locally so you notice drift, strict on production and in CI so a mismatched client can never produce a bad archive:

VANGUARD_PG_VERSION_CHECK=strict
VANGUARD_PG_DUMP_BINARY=/usr/lib/postgresql/17/bin/pg_dump
VANGUARD_PSQL_BINARY=/usr/lib/postgresql/17/bin/psql

With the check active the health screen reports both majors, which is a cheap way to spot a base image upgrade that silently swapped the client.

Cloudflare R2 as the remote destination. Provide a bucket and either an account ID or an explicit endpoint:

VANGUARD_R2_BUCKET=backups
VANGUARD_R2_ACCOUNT_ID=...
# or VANGUARD_R2_ENDPOINT=https://<account>.r2.cloudflarestorage.com
# optional: EU jurisdiction

Vanguard declares an S3-compatible disk named vanguard-r2 (region auto, path-style endpoint) and touches nothing else — s3 included. Two integration patterns:

  • Let Vanguard own the disk and point backup.remote_disk at vanguard-r2.
  • Or declare your own disk name and build its config with Vanguard::r2DiskConfig(), keeping credentials under your own key names. If you already have a disk called vanguard-r2, yours wins.

Either way you need league/flysystem-aws-s3-v3; the health screen and vanguard:install both name it if it is absent.

Dashboard authorization. The default check only asks "is a user logged in". In a multi-tenant app that is not enough — every tenant admin would reach every tenant's backups. Register a gate and point the package at it:

Vanguard::auth(fn ($user) => Gate::forUser($user)->allows('viewVanguard'));

Keep the platform-operator role in your application, not in the package.

Extending the package. BackupManager::createRecord() now takes a fourth optional ?array $sources = null describing what the backup holds (null = configured sources). Subclasses that call it need no change; subclasses that redefine it must add the parameter. If you write archives outside backupLandlord() / backupTenant() / backupFilesystem(), call assertEncryptionReady() before dumping or archiving — otherwise a missing recipient or age binary only surfaces at bundling time, and the run wastes a full dump first.

Gotchas and Tips

The config merge is one level deep — this is the trap of this release. Laravel merges package config one level only, so a nested opt-in key that you did not copy into your published config is never read, even if its env variable is set. Specifically, on a config published by 2.6.x you must manually add:

  • tenancy.database_mode under the existing tenancy section, before VANGUARD_TENANCY_DATABASE_MODE has any effect
  • dump.pgsql_version_check under dump
  • binaries.age under binaries

The brand-new top-level sections (database, encryption, r2) are fine without copying — they merge in whole. Symptom to recognise: you set the env var, php artisan config:show vanguard shows no such key, and the feature appears not to exist. vanguard:install flags exactly these, so run it after adding any nested opt-in.

Encryption has no plaintext fallback, before or after the dump. A missing binary or an empty recipient list fails the backup before the dump starts. Good for integrity, but it means a broken VANGUARD_AGE_BINARY path silently kills the whole schedule rather than degrading — make sure your scheduler's PATH resolves age, or pin it absolutely (which you should do regardless; age from a distro package can be older than the recipients format you generated).

The private key is not stored anywhere by the package. Backups you cannot restore are worse than no backups. Verify a restore drill (--identity=) before you enable encryption in production, and keep the identity out of the same failure domain as the archives.

Encrypted archives are console-only. The restore endpoint and the restore job reject them with a 400 naming the command to run. Do not build an admin "restore" button on the dashboard without handling this path — detect the .age extension in the payload and render instructions instead.

Single-database mode silently changes what a tenant backup is. Tooling, dashboards and runbooks written against multi will assume tenant archives contain data. They now contain files only. Also, a tenant without restore_storage is refused at three separate layers — expect the 400 in tests and don't paper over it.

Row-Level Security dumps are the classic silent-corruption case. Because Vanguard now fails loudly on a missing/mismatched dedicated connection, any custom backup code you have that reaches around backupLandlord() still has the old bug. Audit it.

Strict version check is not a guarantee. An unreadable pg_dump version never blocks — by design, so odd builds don't wedge your schedule. Do not treat strict as proof of a correct client in minimal container images; assert the version yourself in CI if it matters.

Read the drift report categories literally. Missing means the absence can change behaviour (e.g. notifications.mail.to — no recipients, no notifications). New optional settings means nothing to do. Treating the second bucket as work will waste an afternoon republishing config you did not need to touch.

Signature compatibility bites only subclasses that override. If you maintain an in-house BackupManager subclass that redefines createRecord(), PHP will refuse the override until you add ?array $sources = null. Subclasses that merely call it, or call parent::createRecord(), compile fine — a static analysis pass is usually enough to tell which you have.

When in doubt on a new feature, check the health screen first. It now surfaces the pg_dump/server majors, the missing S3 adapter, and the effective state of the landlord/tenant backup paths — that is a faster diagnosis than reading the logs, and it is where the new release put most of its diagnostics.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo