4-Post Series

Building a Laravel Backup System:
Everything I Learned

Four posts documenting a real PHP backup system — from the first design decision to the architecture that made it composable, the failure modes that surprised me, and why restores are the part that actually matters.

4-post series ~20 min total Andrew Judd — developer, consultant

1. Build vs. Buy

Managed backup services exist for good reason. S3 versioning, EBS snapshots, Restic, Veeam, BackupBuddy — if one of these fits your constraints, use it. The engineering cost of building a backup system correctly is substantial, and getting it wrong has consequences that don't surface until you need a restore.

That said, there are real cases where you need something custom. The constraints that pushed me toward building:

Multiple heterogeneous destinations

Backing up to S3 is one API call. Backing up to S3, a local NAS, and a remote SFTP server — with different credentials, retry logic, and verification per destination — requires something composable. Most managed tools handle one or two destination types cleanly and get awkward beyond that.

Application-aware backup logic

Generic backup tools back up files and databases. When you need to snapshot application state — capturing related records across multiple tables consistently, or coordinating a backup window with a running queue — you need code that understands the application.

Custom verification requirements

Some backups need to be tested by actually restoring them, not just by confirming a file was written. If your verification step requires running application code against the restored data, that is not something a generic tool will do for you.

The default answer is still: don't build it

This series documents what building one actually involves. If you read through it and decide a managed service is fine after all, that is the right conclusion. The goal is not to convince you to build — it is to show you what you are signing up for if you do.

2. The Four-Post Arc

Read them in order for the full narrative. Each post stands alone, but the architecture in post 2 only makes sense with the motivation from post 1, and the failure modes in post 4 only land once you understand what was supposed to work.

3. Hard-Won Lessons

These apply whether you build your own or evaluate someone else's:

A backup you have never restored is a hypothesis

Confirming that a backup file was written is not the same as confirming that the data can be recovered. The only way to know a backup works is to restore from it. Teams that discover this distinction during an incident learn it at the most expensive possible time.

The Strategy pattern makes destinations trivially extensible

Once each backup destination implements the same interface, the pipeline that calls them does not care what it is talking to. Adding S3 Glacier alongside the existing S3 Standard destination was a new class, not a modified switch statement. This is the core architectural payoff of the series.

Silent failures are worse than loud ones

A backup job that throws an exception is easy to respond to. A backup job that completes with status "success" but wrote a corrupted or truncated file will not surface until a restore is attempted. Verification needs to be meaningful — reading back and validating, not just confirming file existence.

Multiple destinations protect against different failure modes

S3 protects against local hardware failure. A local NAS protects against S3 regional outages or account compromises. An offsite SFTP protects against the scenarios where both fail simultaneously. Each destination adds protection against a specific class of failure, not just redundancy.

The restore path is a different problem than the backup path

Most backup systems are designed around writing. The restore path — locating the right backup, transferring it, re-inflating it, validating integrity, applying it to a running or recovering system — is a separate problem with its own failure modes. Design both paths before you ship either.

4. The Architecture

The full design walkthrough is in The Pipeline Strategy Pattern. The short version:

1

A BackupDestination interface

Every destination — S3, local disk, SFTP — implements the same interface. The pipeline calls the interface; it does not care what is behind it.

2

A pipeline of stages

Each backup runs through: snapshot → compress → encrypt → transfer → verify. Each stage is independent; failures short-circuit the rest and report specifically.

3

The pipeline runs once per destination

The same pipeline, the same stages, the same verification — against each registered destination in sequence. A new destination is a new Strategy class, registered in config. No pipeline changes.

4

Restoration is a first-class path

Each destination also implements restore(). The restore pipeline is the inverse of the backup pipeline: locate → transfer → decrypt → decompress → verify → apply. Both paths are tested on a schedule.

Start from the beginning

I Built My Own Backup System — the constraints that made this necessary

5. Frequently Asked Questions

Should I build a backup system or use a managed service?

Use a managed service by default — S3 versioning, snapshots, or a tool like Restic cover most cases. Build your own only when you have real constraints a managed service can not meet: specific destinations, custom verification, or tight integration with application state. This series documents the full cost of building correctly so you can make the decision clearly.

What is the Pipeline Strategy Pattern in PHP?

The Pipeline pattern passes data through a sequence of stages; the Strategy pattern swaps implementations behind a common interface. Combined: each backup destination is a Strategy, and the backup job is a Pipeline that runs the same stages (compress, encrypt, transfer, verify) for each one. Adding a new destination means writing one class, not modifying existing code.

How do I add a new backup destination without breaking existing ones?

Implement the BackupDestination interface. The pipeline calls the interface — it does not know which concrete implementation it is talking to. Existing destinations are untouched. This is the central design payoff of Post 2.

Why are restores harder than backups?

Backups are controlled write operations. Restores happen under pressure, in degraded conditions, often to a different environment. They require re-inflating data, handling schema drift, re-establishing connections, and validating integrity — all while something is broken. Post 3 covers this in full.

How often should I test my restores?

At minimum monthly, and on every significant schema or data-format change. A backup you have never restored is a hypothesis. Automated restore tests that validate data integrity are the gold standard; a manual spot-check on a cadence is vastly better than nothing.

Need a Backup System Built or Reviewed?

I've been building and operating production systems for over 18 years.

Whether you need a backup system designed from scratch, an existing one audited for correctness, or a reliable restore path — this work is often part of a broader small business automation project. Most of these conversations are short.