---
title: >-
  Practical Lab: Creating and Restoring a Local Shopware Backup | Shopware
  Community Hub
description: >-
  Create, validate, and restore a local Shopware backup using Shopware CLI or
  Docker Compose.
canonical_url: >-
  https://hub.shopware.com/learn/unit/practical-lab-local-shopware-backup-and-restore
---

<LearningObjectives>

- Choose the local backup and restore workflow that matches a Shopware CLI or Docker Compose project.
- Create a timestamped recovery point containing a valid database dump and the matching project files.
- Validate a local backup before using it and distinguish between a test restore and a rollback of an existing project.
- Restore the project files, dependencies, and database, then verify the storefront, administration, known data, and included media.
- Apply basic backup safety practices and recognize the limits of this local workflow for production environments.

</LearningObjectives>

# Practical Lab: Creating and Restoring a Local Shopware Backup

In the previous learning unit, you learned why a Shopware backup should include both the database and the relevant project files.

This practical lab turns that overview into a working local backup and restore process. You will create a timestamped snapshot, verify that it contains a valid database dump and the matching project files, and restore it into a safe local target.

You can complete the lab with Shopware CLI or directly with Docker Compose. Both workflows create the same backup structure, so follow only the one that matches how your local project is managed.

The practical lab is intentionally limited to local development. It teaches the mechanics of creating and testing a recovery point before an update; **it does not replace a production backup strategy**.

## Local Backup and Restore

The following workflows guide you through creating and restoring the **manual local backup** described above.

<Callout title="Local Development Only" type="warning">

The commands in this learning unit are for a local development setup.

In production, database names, service names, users, and passwords are usually different. Production systems also need automated backups, secure storage, access control, retention rules, and tested restore procedures.

</Callout>

### Target Result

By the end of the workflow, you will have created a timestamped local backup with the following structure:

```text
my-shop-project-backup-YYYYMMDD-HHMMSS/
|-- backup.sql
`-- project/
```

The `backup.sql` file contains the database snapshot. The `project/` directory contains the matching project files without generated dependencies, caches, logs, or Git metadata.

You will then use this snapshot to perform one of two restore scenarios:

- Restore it into a new directory to test the backup without overwriting the existing project.
- Restore it into the existing project directory to roll that project back.

### Before You Start

Make sure that:

- Shopware is installed in the local project and its database contains the Shopware tables. Starting an empty database container does not install Shopware.
- `rsync` is available in your shell. It is included with macOS and most Linux distributions. Verify it with `rsync --version`.
- The project can be managed either with Shopware CLI or directly with Docker Compose.
- You know the root directory of the Shopware project.

Complete only the workflow that matches your project. Keep the same terminal session open while following it because later commands reuse variables such as `BACKUP_DIR`.

### What the Local Backup Includes

Before running commands, it helps to know what you are trying to protect.

A Shopware project has two important parts:

- **Database**: Contains products, customers, orders, configuration, plugin data, and many other records.
- **Project files**: Contain the Shopware project, custom code, configuration files, uploaded files, and other runtime files depending on your setup.

For a local rollback before an update, you usually want both parts. A database dump without matching project files may not be enough if the update changed code, configuration, or plugin files.

A database-only backup can be sufficient when the exact source-code revision, dependency lock files, configuration, and uploaded files are already protected elsewhere. Reinstalling Shopware alone does not restore these project-specific files or uploaded media.

The workflow in this learning unit therefore creates a database dump and a lean project snapshot from the same point in time. Generated dependencies such as `vendor/` and `node_modules/` are excluded because they can be restored from their lock files. Cache and log files are excluded because they are not part of the rollback state.

<Callout title="What This Is and What This Is Not" type="info">

This workflow creates a manual backup of a local project folder and its local database.

It is not an automated backup system, not a Shopware extension, and not a complete production disaster-recovery strategy.

</Callout>

### Choose Your Local Workflow

The following workflows create the same backup structure, but they use different tools. Complete only the workflow that matches your local project:

- **Local backup via Shopware CLI** uses the configured project environment and resolves the database connection for you.
- **Local backup via Docker Compose without Shopware CLI** runs the database tools directly inside the database container.

Do not combine commands from both workflows for the same backup.

## Local Backup via Shopware CLI

Use the following workflow when the project was created or is managed with Shopware CLI.

### Step 1: Start the Project Environment

Run all commands from the root directory of the Shopware project.

Start the development environment in the background:

```bash
shopware-cli project dev start
```

Running `shopware-cli project dev` on its own opens the interactive dashboard instead. It takes over the terminal, and the later steps rely on shell variables in this session. If you use the dashboard, leave it with `q` and choose **Quit, keep running** so that the environment stays up.

Check whether the environment is running:

```bash
shopware-cli project dev status
```

### Step 2: Create the Local Backup

Create a new timestamped backup directory next to the project:

```bash
PROJECT_NAME="$(basename "$PWD")"
BACKUP_DIR="../${PROJECT_NAME}-backup-$(date +%Y%m%d-%H%M%S)"
mkdir "$BACKUP_DIR"
```

Keep the same terminal session open so that the `BACKUP_DIR` variable remains available.

Export the database directly into the backup directory:

```bash
shopware-cli project dump --output "$BACKUP_DIR/backup.sql"
```

Shopware CLI uses the connection details of the current project environment. For the common local case, you do not need to enter the database service name, host, user, or password.

If the dump is created successfully, the output should look similar to this:

```text
INFO    Successfully created the dump ../my-shop-project-backup-YYYYMMDD-HHMMSS/backup.sql
```

The project name and timestamp in your path will be different.

Copy the project files:

```bash
rsync -aH \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  ./ "$BACKUP_DIR/project/"
```

Verify that the dump and project snapshot exist:

```bash
ls -lh "$BACKUP_DIR/backup.sql"
ls -la "$BACKUP_DIR/project"
```

The first command should show an SQL file that contains table definitions. The second command should list the project files, including entries such as `bin`, `composer.json`, `composer.lock`, `config`, `custom`, `public`, and `src`. File sizes, timestamps, and the exact list depend on your project.

Run this additional check to confirm the two required backup components:

```bash
if grep -q '^CREATE TABLE' "$BACKUP_DIR/backup.sql" && [ -f "$BACKUP_DIR/project/composer.json" ]; then
  echo "Database dump and project snapshot found."
else
  echo "Backup is incomplete."
fi
```

A complete backup produces this output:

```text
Database dump and project snapshot found.
```

The backup directory now contains:

```text
my-shop-project-backup-YYYYMMDD-HHMMSS/
|-- backup.sql
`-- project/
```

<Callout title="The Project Directory Cannot Be Found" type="warning">

If `rsync` later reports an error such as:

```text
rsync: [sender] change_dir ".../shopware-backup-.../project" failed: No such file or directory (2)
```

Check the selected backup directory:

```bash
ls -la "$BACKUP_DIR"
```

A backup created with this workflow must contain both `backup.sql` and the `project/` directory. A backup created with an older or different command may store the project files directly in the backup root. Do not continue with the restore until `BACKUP_DIR` points to a backup with the expected structure.

</Callout>

<Callout title="Optional Shopware CLI Dump Variants" type="info">

For a local rollback before an update, use the normal dump shown above.

- `--clean` excludes data from transient tables such as carts, logs, and message queues. This creates a smaller development dump, but not a complete copy of the current database state.
- `--anonymize` anonymizes known customer data. Use it when data must be transferred to a shared development or staging environment, not for an unchanged rollback copy.

View the options available in your installed Shopware CLI version with:

```bash
shopware-cli project dump --help
```

</Callout>

### Optional: Combine the Shopware CLI Backup Steps

The combined command below is an alternative to the individual backup steps shown above. Do not run it immediately after creating the same backup step by step. Use it for a future backup after you understand and have verified the individual commands:

```bash
PROJECT_NAME="$(basename "$PWD")" && \
BACKUP_DIR="../${PROJECT_NAME}-backup-$(date +%Y%m%d-%H%M%S)" && \
mkdir "$BACKUP_DIR" && \
shopware-cli project dump --output "$BACKUP_DIR/backup.sql" && \
rsync -aH \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  ./ "$BACKUP_DIR/project/"
```

### Step 3: Prepare the Restore

Restoring a snapshot replaces the current project and database state. Use a backup folder whose project files and database dump were created together.

The project snapshot already contains the Shopware project files. You do not need to create another project with `shopware-cli project create` before restoring it. Choose one of the following restore targets:

- Restore into a **new empty directory** to test the backup without overwriting the existing project.
- Restore into the **existing project directory** to roll that project back.

In both cases, start from the root directory of the existing Shopware project and stop its environment first:

```bash
shopware-cli project dev stop
```

List the available backup directories:

```bash
PROJECT_NAME="$(basename "$PWD")"
ls -d "../${PROJECT_NAME}-backup-"*
```

Select the backup that you want to restore. Replace the example name with one of the directories shown by the previous command:

```bash
BACKUP_DIR="../my-shop-project-backup-20260925-190121"
```

Validate the selection and resolve it to an absolute path:

```bash
if grep -q '^CREATE TABLE' "$BACKUP_DIR/backup.sql" 2>/dev/null && [ -f "$BACKUP_DIR/project/composer.json" ]; then
  BACKUP_DIR="$(cd "$BACKUP_DIR" && pwd)"
  echo "Using backup: $BACKUP_DIR"
else
  echo "Selected backup is incomplete or does not exist. Do not continue."
fi
```

A valid selection produces output similar to this:

```text
Using backup: /home/user/projects/my-shop-project-backup-20260925-190121
```

Confirm that this directory contains the matching database dump and project snapshot:

```bash
ls -lh "$BACKUP_DIR/backup.sql"
ls -la "$BACKUP_DIR/project"
```

### Step 4: Option A (Recommended): Restore Into a New Directory

This is the recommended option for your first restore test because it verifies the backup without changing the existing project.

Create an empty directory next to the existing project, copy the project snapshot into it, enter the restored project, and give it its own Docker Compose project name:

```bash
RESTORE_DIR="../${PROJECT_NAME}-restored-$(date +%Y%m%d-%H%M%S)"
mkdir "$RESTORE_DIR"
RESTORE_DIR="$(cd "$RESTORE_DIR" && pwd)"
rsync -aH "$BACKUP_DIR/project/" "$RESTORE_DIR/"
cd "$RESTORE_DIR"
grep -v '^COMPOSE_PROJECT_NAME=' .env > .env.tmp && mv .env.tmp .env
```

The last command removes the `COMPOSE_PROJECT_NAME` that Shopware CLI stores in `.env`. Without it, Docker Compose would reuse the containers and the database volume of the original project. The restored directory is now the Shopware project root. Continue with **Step 5: Complete the Restore** below.

### Step 4: Option B: Roll Back the Existing Project Directory

Use this option when you intentionally want to roll back the current project. Stay in the root directory of that project. In the following commands, `./` refers to the current project directory.

First, preview which project files would be copied or removed:

```bash
rsync -aHni --delete \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  "$BACKUP_DIR/project/" ./
```

The `-n` option performs a dry run and does not change any files. The `-i` option lists the differences that `rsync` finds.

If you created the backup immediately before this check and have not changed the project since then, the command may produce no output. This means that `rsync` did not find any differences. If files differ, the output may look similar to this:

```text
>f.st...... composer.lock
*deleting   old-project-file.txt
```

Review every listed change carefully.

The rollback must also replace the database completely so that tables created after the backup do not survive the import. Once the dry run shows only the expected file changes, remove the named volumes declared in the current Compose file:

```bash
shopware-cli project dev stop --remove-data
```

The environment is already stopped, so this command removes its named volumes without stopping it again. In the standard Shopware CLI setup, this includes the database volume. If your project declares additional named volumes, the command removes those as well.

Now restore the files without the `-n` option:

```bash
rsync -aHi --delete \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  "$BACKUP_DIR/project/" ./
```

Here, `--delete` removes files from the active project when they are not part of the backup snapshot. The excluded directories remain untouched.

If the dry run did not list any differences, the actual `rsync` command may also finish without output. This is expected when the project already matches the snapshot.

### Step 5: Complete the Restore

At this point, your current directory must be the root of either the separately restored project or the existing project that you rolled back.

Start the restored project environment and install the dependencies from its `composer.lock` file:

```bash
shopware-cli project dev start
shopware-cli project composer install
```

The output should confirm that the development environment started and that Composer installed or verified the dependencies from the lock file. It should look similar to this:

```text
Development environment started
Shop URL:  http://127.0.0.1:8000
Admin URL: http://127.0.0.1:8000/admin

Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating optimized autoload files
Executing script assets:install [OK]
```

URLs and Composer messages can differ depending on the project state.

Import the matching database dump:

```bash
shopware-cli project sql --file "$BACKUP_DIR/backup.sql"
```

The import can produce many lines. Successful statements look like this:

```text
...
Query OK, 0 rows affected
Query OK, 1 row affected
Query OK, 100 rows affected
...
```

Different row counts are expected. The important result is that the command completes without an `ERROR` message.

Clear the cache:

```bash
shopware-cli project console cache:clear
```

A successful cache clear ends with output similar to this:

```text
// Finished

[OK] Cache for the "dev" environment (debug=true) was successfully cleared.
```

Finally, verify the restored Shopware project. A successful import or cache clear alone does not prove that the complete shop works as expected:

1. Open the storefront URL reported by Shopware CLI and confirm that the storefront loads without an error.
2. Open the administration and confirm that you can log in.
3. Check a product, configuration value, or other known record that existed when you created the backup.
4. If the snapshot includes uploaded media, confirm that one of those files is displayed correctly.

### Optional: Clean Up the Test Restore

If you restored into a new directory, its environment keeps running and uses the same host ports as the original project. Stop it, remove its data, and delete the restored directory before you start the original project again. Otherwise `shopware-cli project dev start` reports that the ports are already in use:

```bash
shopware-cli project dev stop --remove-data
cd ..
rm -rf "$RESTORE_DIR"
```

Then change into the original project directory and start its environment again.

## Local Backup via Docker Compose Without Shopware CLI

Use this workflow when you operate the local Shopware environment directly with Docker Compose and do not use Shopware CLI for the backup.

### Step 1: Check the Docker Compose Setup

Run all commands from the root directory of the Shopware project.

Start the environment with the project's `Makefile` if it provides one:

```bash
make up
```

If the required images are already available locally, the first start creates the project's network, database volume, and containers. Successful output looks similar to this:

```text
[+] up 6/6
✔ Network shopware_docker_default      Created
✔ Volume shopware_docker_db-data       Created
✔ Container shopware_docker-database-1 Healthy
✔ Container shopware_docker-mailer-1   Started
✔ Container shopware_docker-adminer-1  Started
✔ Container shopware_docker-web-1      Started
```

If an image is not available locally, Docker pulls it first. In that case, additional `Pulled` lines appear and the progress count is higher. The generated project prefix, container order, progress count, and timings can differ. The important result is that the database becomes `Healthy` and the other required containers are `Started` or `Running`.

If the project does not provide a `make up` command, for example a project created with `shopware-cli project create`, whose `compose.yaml` is managed by Shopware CLI, start Docker Compose directly instead:

```bash
docker compose up -d
```

When the images, network, volume, and containers already exist, the output is shorter:

```text
[+] up 4/4
✔ Container shopware_docker-mailer-1   Running
✔ Container shopware_docker-database-1 Healthy
✔ Container shopware_docker-web-1      Running
✔ Container shopware_docker-adminer-1  Running
```

You only need to use one of these two start commands. In this setup, `make up` runs `docker compose up -d` through the project's `Makefile`.

List the configured services:

```bash
docker compose config --services
```

For the example setup, the output looks like this:

```text
database
adminer
mailer
web
```

The following example assumes:

- The database service is called `database`.
- The application service is called `web`.
- The database name is `shopware`.
- The local database user and password are both `root`.

Check your `compose.yaml` and environment configuration. Adapt the following commands if your project uses different service names or credentials.

Inspect the running containers:

```bash
docker compose ps
```

The relevant columns should look similar to this:

```text
NAME                         IMAGE                                             COMMAND                  SERVICE    CREATED          STATUS                    PORTS
shopware_docker-adminer-1    adminer                                           "entrypoint.sh docke…"   adminer    27 seconds ago   Up 26 seconds             0.0.0.0:9080->8080/tcp, [::]:9080->8080/tcp
shopware_docker-database-1   mariadb:11.8                                      "docker-entrypoint.s…"   database   27 seconds ago   Up 26 seconds (healthy)   0.0.0.0:32780->3306/tcp, [::]:32780->3306/tcp
shopware_docker-mailer-1     axllent/mailpit                                   "/mailpit"               mailer     27 seconds ago   Up 26 seconds (healthy)   1110/tcp, 127.0.0.1:8025->8025/tcp, 0.0.0.0:32781->1025/tcp, [::]:32781->1025/tcp, 0.0.0.0:32782->8025/tcp, [::]:32782->8025/tcp
shopware_docker-web-1        ghcr.io/shopware/docker-dev:php8.4-node24-caddy   "/entrypoint /usr/bi…"   web        27 seconds ago   Up 23 seconds             0.0.0.0:5173->5173/tcp, [::]:5173->5173/tcp, 0.0.0.0:5773->5773/tcp, [::]:5773->5773/tcp, 0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp, 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp, 0.0.0.0:9998-9999->9998-9999/tcp, [::]:9998-9999->9998-9999/tcp, 9000/tcp
```

Docker also prints a `PORTS` column. Host ports may be assigned dynamically, so those values can differ between systems. The important checks are that the expected images are used, all services are `Up`, and the database is `healthy`.

### Step 2: Select the Database Tools

Check which database image the project uses:

```bash
docker compose images database
```

You should see a similar console output:

```text
CONTAINER                    REPOSITORY    TAG        PLATFORM         IMAGE ID         SIZE             CREATED
shopware_docker-database-1   mariadb       11.8       linux/amd64      48f7ae354376     329MB            2 weeks ago
```

Now, select the command-line tools that belong to that image. Keep the same terminal session open so that these variables remain available.

For a MariaDB image, such as `mariadb:11.8`, execute these lines. MariaDB is the default database of the Docker setup from the Shopware Setup course and of projects created with `shopware-cli project create`:

```bash
DB_DUMP_TOOL="mariadb-dump"
DB_CLIENT_TOOL="mariadb"
```

For a MySQL image, such as `mysql:8.4`, use:

```bash
DB_DUMP_TOOL="mysqldump"
DB_CLIENT_TOOL="mysql"
```

Using a tool that is not included in the database image causes the dump command to fail.

### Step 3: Create the Local Backup

Create a new timestamped backup directory next to the project:

```bash
PROJECT_NAME="$(basename "$PWD")"
BACKUP_DIR="../${PROJECT_NAME}-backup-$(date +%Y%m%d-%H%M%S)"
mkdir "$BACKUP_DIR"
```

Keep the same terminal session open so that the `BACKUP_DIR` variable remains available.

Create the database dump:

```bash
docker compose exec -T database "$DB_DUMP_TOOL" -uroot -proot shopware > "$BACKUP_DIR/backup.sql"
```

A successful dump command normally produces no console output because `>` writes the SQL output into `backup.sql`.

<Callout title="Password Warning With MySQL" type="info">

When using a MySQL image and the `mysqldump` or `mysql` client, the command can print:

```text
mysqldump: [Warning] Using a password on the command line interface can be insecure.
```

This warning is expected in this local training workflow and does not by itself mean that the command failed. MariaDB 11.8 normally does not print the corresponding warning.

Continue only after the SQL validation below succeeds. Passing credentials on the command line is not suitable for production environments.

</Callout>

The command has the following parts:

- `docker compose exec -T database` runs the command in the `database` service without allocating a pseudo-TTY.
- `"$DB_DUMP_TOOL"` uses the dump tool selected for the database image.
- `-uroot -proot` supplies the local database user and password.
- `shopware` selects the database.
- `> "$BACKUP_DIR/backup.sql"` writes the output to the backup directory on the host machine.

Verify that the file contains SQL table definitions before you continue:

```bash
if grep -q '^CREATE TABLE' "$BACKUP_DIR/backup.sql"; then
  echo "Database dump contains table definitions."
else
  echo "Database dump is invalid. Do not continue."
fi
```

A valid, non-empty Shopware database dump produces this output:

```text
Database dump contains table definitions.
```

If the dump command succeeds but this validation reports `Database dump is invalid`, inspect the file size. A small SQL file containing only dump headers usually means that the database server was available but the selected database contained no tables.

Copy the project files:

```bash
rsync -aH \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  ./ "$BACKUP_DIR/project/"
```

Verify that the dump and project snapshot exist:

```bash
ls -lh "$BACKUP_DIR/backup.sql"
ls -la "$BACKUP_DIR/project"
```

Run this final completeness check:

```bash
if grep -q '^CREATE TABLE' "$BACKUP_DIR/backup.sql" && [ -f "$BACKUP_DIR/project/composer.json" ]; then
  echo "Database dump and project snapshot found."
else
  echo "Backup is incomplete."
fi
```

A complete backup produces this output:

```text
Database dump and project snapshot found.
```

<Callout title="The Selected Database Tool Does Not Exist" type="warning">

If the selected dump tool is not available in the database image, `backup.sql` can contain an error instead of SQL. For example:

```text
OCI runtime exec failed: exec: "mariadb-dump": executable file not found in $PATH
```

Do not continue with this file. Check the database image and select the matching tools. If a combined backup command fails at this step, `&&` prevents the project snapshot from being copied. This is why the resulting backup directory may contain `backup.sql` but no `project/` directory.

</Callout>

### Optional: Combine the Docker Compose Backup Steps

The combined command below is an alternative to the individual backup steps shown above. Do not run it immediately after creating the same backup step by step. Use it for a future backup after you understand and have verified the individual commands:

```bash
PROJECT_NAME="$(basename "$PWD")" && \
BACKUP_DIR="../${PROJECT_NAME}-backup-$(date +%Y%m%d-%H%M%S)" && \
mkdir "$BACKUP_DIR" && \
docker compose exec -T database "$DB_DUMP_TOOL" -uroot -proot shopware > "$BACKUP_DIR/backup.sql" && \
grep -q '^CREATE TABLE' "$BACKUP_DIR/backup.sql" && \
rsync -aH \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  ./ "$BACKUP_DIR/project/"
```

The combined command may finish without console output because the dump is redirected to a file, `grep` is silent when the validation succeeds, and `rsync` is not running in verbose mode. Confirm the result with the completeness check shown above. If the command stops before creating `project/`, inspect `backup.sql` for an error and verify `DB_DUMP_TOOL`.

### Step 4: Prepare the Restore

Restoring a snapshot replaces the current project and database state. Use a backup folder whose project files and database dump were created together.

The project snapshot already contains the Shopware project files and `compose.yaml`. You do not need to create a fresh Shopware project before restoring it. Choose whether you want to test the backup in a new directory or roll back the existing project.

Start from the root directory of the existing project and stop its Docker Compose environment:

```bash
docker compose down
```

List the available backup directories:

```bash
PROJECT_NAME="$(basename "$PWD")"
ls -d "../${PROJECT_NAME}-backup-"*
```

Select one of the listed directories. Replace the example name with the backup that you want to restore:

```bash
BACKUP_DIR="../my-shop-project-backup-20260928-115836"
```

Validate the selected backup and resolve it to an absolute path:

```bash
if grep -q '^CREATE TABLE' "$BACKUP_DIR/backup.sql" 2>/dev/null && [ -d "$BACKUP_DIR/project" ]; then
  BACKUP_DIR="$(cd "$BACKUP_DIR" && pwd)"
  echo "Using backup: $BACKUP_DIR"
else
  echo "Selected backup is incomplete or does not exist. Do not continue."
fi
```

A valid selection prints the absolute path. Confirm its contents:

```bash
ls -lh "$BACKUP_DIR/backup.sql"
ls -la "$BACKUP_DIR/project"
```

<Callout title="The Selected Backup Does Not Exist" type="warning">

Do not copy an example timestamp without checking that the corresponding directory exists. If the shell reports `cd: no such file or directory`, `BACKUP_DIR` may become empty. Later paths would then incorrectly resolve to `/backup.sql` or `/project`.

Select an existing directory from the project-specific backup list and repeat the validation before continuing.

</Callout>

If you opened a new terminal session, select `DB_CLIENT_TOOL` again according to the database image before continuing.

Choose **one** of the following restore methods:

- Use **Restore Into a New Directory** to test the backup without replacing the existing project directory.
- Use **Restore Into the Existing Project Directory** to roll the existing project files back to the snapshot.

Do not perform both methods as consecutive steps. After completing your chosen method, continue with **Step 6: Complete the Restore**.

### Step 5: Option A (Recommended): Restore Into a New Directory

This is the recommended option for your first restore test because it verifies the backup without changing the existing project.

Remain in the root directory of the existing Shopware project. Do not run `cd ..` and do not create or enter a restore directory manually. The following commands create a new restore directory next to the existing project, resolve it to an absolute path, copy the project snapshot into it, and enter it.

First, confirm that the current directory is still the Shopware project root:

```bash
pwd
test -f compose.yaml && test -f composer.json && echo "Shopware project root confirmed."
```

Expected output resembles:

```text
/home/user/projects/my-shop-project
Shopware project root confirmed.
```

If the confirmation message is missing, return to the existing Shopware project root before continuing. Then create the separate restore target:

```bash
RESTORE_DIR="../${PROJECT_NAME}-restored-$(date +%Y%m%d-%H%M%S)"
mkdir "$RESTORE_DIR"
RESTORE_DIR="$(cd "$RESTORE_DIR" && pwd)"

echo "Backup source: $BACKUP_DIR"
echo "Restore target: $RESTORE_DIR"

if [ "$BACKUP_DIR" = "$RESTORE_DIR" ]; then
  echo "Backup source and restore target must be different. Do not continue."
else
  rsync -aH "$BACKUP_DIR/project/" "$RESTORE_DIR/" && cd "$RESTORE_DIR"
fi
```

`PROJECT_NAME` is the project name determined earlier while listing the available backups. Do not assign it again after leaving the project root. The two printed paths must be different. The backup directory is the read-only source of the restore and must continue to contain `backup.sql` and `project/`. Do not start Docker Compose in the backup directory and do not restore the contents of `project/` into its parent backup directory.

Give the restored copy its own Docker Compose project. Projects created with Shopware CLI store a `COMPOSE_PROJECT_NAME` in `.env`, and the snapshot contains that file. Without this step, Docker Compose would attach the restored directory to the containers and the database volume of the original project:

```bash
grep -v '^COMPOSE_PROJECT_NAME=' .env > .env.tmp && mv .env.tmp .env
```

Confirm that you are now in the restored project and that the original backup is still complete:

```bash
pwd
ls -lh "$BACKUP_DIR/backup.sql"
test -f composer.json && echo "Restored project files found."
```

Expected output resembles:

```text
/home/user/projects/my-shop-project-restored-20260928-120139
-rw-r--r-- 1 user user 2.1M Sep 28 11:58 /home/user/projects/my-shop-project-backup-20260928-115836/backup.sql
Restored project files found.
```

Continue with **Step 6: Complete the Restore** below.

### Step 5: Option B: Roll Back the Existing Project Directory

Use this method instead of the new-directory method when you want to replace the files in the existing project with the snapshot. Stay in the root directory of the existing Shopware project.

First, compare the absolute backup and project paths:

```bash
CURRENT_PROJECT_DIR="$(pwd -P)"

echo "Backup source: $BACKUP_DIR"
echo "Restore target: $CURRENT_PROJECT_DIR"

if [ "$BACKUP_DIR" = "$CURRENT_PROJECT_DIR" ]; then
  echo "The backup directory cannot be used as the restore target. Do not continue."
else
  echo "Backup source and restore target are different. The dry run can continue."
fi
```

Expected output resembles:

```text
Backup source: /home/user/projects/my-shop-project-backup-20260928-115836
Restore target: /home/user/projects/my-shop-project
Backup source and restore target are different. The dry run can continue.
```

The paths must be different. If the stop message appears, return to the actual Shopware project directory and repeat the check. Do not run `rsync` from inside the backup directory.

When the safety check succeeds, preview which files would be copied, updated, or removed:

```bash
rsync -aHni --delete \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  "$BACKUP_DIR/project/" ./
```

The `-n` option performs a dry run without changing files, while `-i` lists the detected differences. The `--delete` option reports files that exist in the current project but not in the snapshot; the excluded directories are not removed.

If the backup was created from this project moments ago and neither copy has changed, the dry run may produce no output. That means `rsync` found no differences. Otherwise, review every listed change carefully.

When the preview contains only the changes you expect, run the same command without `-n` to perform the restore:

```bash
rsync -aHi --delete \
  --exclude '.git/' \
  --exclude '/vendor/' \
  --exclude 'node_modules/' \
  --exclude 'var/cache/' \
  --exclude 'var/log/' \
  "$BACKUP_DIR/project/" ./
```

If the dry run found no differences, the restore command also normally produces no output.

### Step 6: Complete the Restore

At this point, your current directory must be the root of either the separately restored project or the existing project that you rolled back.

Before starting containers, verify once more that the current project directory is not the backup directory and that the dump is still available:

```bash
echo "Backup source: $BACKUP_DIR"
echo "Current project: $(pwd -P)"
ls -lh "$BACKUP_DIR/backup.sql"
```

If the two paths are identical, or if `backup.sql` cannot be found, do not continue. Select the intact backup again and resolve `BACKUP_DIR` to an absolute path as described above.

Make sure the Docker Compose environment is running and install the dependencies from the restored `composer.lock` file:

```bash
docker compose up -d
docker compose exec -T web composer install
```

Docker Compose should report the database as `Healthy` and the remaining services as `Started` or `Running`. Composer then installs the versions recorded in `composer.lock`. In an existing project whose `vendor/` directory already matches the lock file, the relevant output resembles:

```text
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating optimized autoload files
Executing script assets:install [OK]
```

When restoring into a new directory, `vendor/` is not present because it was excluded from the snapshot. Composer therefore downloads and installs the dependencies before generating the autoload files. Both outcomes are valid.

Next, replace the current local database with the matching dump. This is destructive: Dropping the database ensures that tables added after the backup do not remain in the restored state.

```bash
docker compose exec -T database "$DB_CLIENT_TOOL" -uroot -proot \
  -e "DROP DATABASE IF EXISTS shopware; CREATE DATABASE shopware;" && \
docker compose exec -T database "$DB_CLIENT_TOOL" -uroot -proot shopware \
  < "$BACKUP_DIR/backup.sql" && \
docker compose exec -T database "$DB_CLIENT_TOOL" -uroot -proot shopware \
  -e "SHOW TABLES LIKE 'plugin';"
```

Each command runs only when the preceding command succeeds. The import itself normally produces no progress output. The final query should confirm that the restored database contains the `plugin` table:

```text
Tables_in_shopware (plugin)
plugin
```

When using MySQL, the password warning described in **Step 3: Create the Local Backup** can additionally appear for each client invocation.

Do not continue to the cache command if the shell reports that `backup.sql` does not exist, the database import prints an error, or the table check returns no result. A cache clear cannot repair a missing database import.

After the import and table check succeed, clear the cache:

```bash
docker compose exec -T web bin/console cache:clear
```

A successful cache clear ends with output similar to:

```text
// Finished

[OK] Cache was successfully cleared.
```

The restored snapshot already contains the built project assets, and `composer install` runs Shopware's asset installation script. A complete frontend rebuild with `bin/build-js.sh` is therefore not part of this restore workflow. Run a separate build only when you intentionally need to regenerate the Administration or Storefront assets.

<Callout title="Many Deletions or rsync Code 24" type="warning">

If `rsync` prints many `*deleting` lines from the backup itself and ends with `some files vanished before they could be transferred (code 24)`, the backup directory was most likely also used as the restore target. Stop the restore. Do not start containers in that directory, because generated files can change while `rsync` is reading them.

Choose an intact backup that still contains both `backup.sql` and `project/`, create a separate `*-restored-*` directory, and repeat the restore from there.

</Callout>

Finally, verify the restored Shopware project. A successful database import or cache clear alone does not prove that the complete shop works as expected:

1. Open the storefront and confirm that it loads without an error.
2. Open the administration and confirm that you can log in.
3. Check a product, configuration value, or other known record that existed when you created the backup.
4. If the snapshot includes uploaded media, confirm that one of those files is displayed correctly.

### Optional: Clean Up the Test Restore

If you restored into a new directory, its containers keep running and use the same host ports as the original project. Stop them, remove their volumes, and delete the restored directory before you start the original project again. Otherwise `docker compose up -d` reports that the ports are already allocated:

```bash
docker compose down -v
cd ..
rm -rf "$RESTORE_DIR"
```

Then change into the original project directory and start its environment again.

## Backup Safety and Common Pitfalls

A backup is useful only if it is complete, protected, and restored to the correct location. Before relying on a local backup, perform the following three checks.

### Check 1: Is the Snapshot Complete?

The database and project files represent one point in time. Keep `backup.sql` and `project/` together in their timestamped backup directory. Mixing files from different backups can produce a project whose code and database schema no longer match.

If the project uses external object storage, the project snapshot does not contain those media files. Back them up separately as part of the same recovery point.

The excluded `vendor/` and `node_modules/` directories contain generated dependencies rather than the project source. Recreate them from the restored project and its lock files when required.

### Check 2: Is the Backup Protected?

Create a new timestamped directory for every backup instead of reusing an existing one. This keeps snapshots separate and prevents newer files from being mixed into an older recovery point.

Keep the backup outside the project repository and restrict access to it. A database dump can contain customer data, while configuration files can contain credentials or other secrets. Do not commit either one to version control.

### Check 3: Is the Restore Target Correct?

A restore changes the current project files and database data. Before proceeding, confirm these two paths:

- **Backup source**: The timestamped directory that contains `backup.sql` and `project/`.
- **Restore target**: The local Shopware project that you want to restore or roll back.

These paths must be different. Never use the backup directory itself as the restore target.

For an existing project, review the `rsync` dry run before running the command with `--delete`. Continue only when the listed changes belong to the intended local project.

### Local Backup or Production Backup?

The workflows in this learning unit create a manual recovery point for local development. They are useful before testing an update, but they are not a complete production backup strategy.

Production requires automated backups, secure off-site storage, retention rules, monitoring, and regularly tested recovery procedures.

## Summary

Well done! In this practical lab, you:

- Selected the local workflow that matches a Shopware CLI or Docker Compose project.
- Created a timestamped recovery point containing a database dump and the matching project files.
- Validated the database dump and project snapshot before relying on the backup.
- Restored the snapshot into a new directory for testing or used it to roll back an existing local project.
- Reinstalled the required dependencies, imported the database, cleared the cache, and verified the restored shop.
- Kept the backup source separate from the restore target and treated its contents as sensitive data.
- Distinguished this manual local workflow from a production backup strategy.

With this workflow, you can create and test a local recovery point before updating a Shopware project. For production environments, extend the same principle with automation, secure off-site storage, retention rules, monitoring, and regular restore tests.
