# Backups

> Back up a Compose installation on a schedule, restore it step by step and prove the restore works.

Source: https://docs.grovs.io/docs/self-hosting/operate/backups

A complete backup is the `.env` file plus three data stores. Without the `.env` encryption keys, a database backup cannot be read back. Run the commands on this page from the install directory: `./grovs` for the installer, `/opt/grovs` as root on a cloud VM.

## What to back up

| Item         | Lives in                 | Contains                                                | Method                                                                                    |
| ------------ | ------------------------ | ------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `.env`       | Install directory        | Every secret, including the at-rest encryption keys     | File copy, stored encrypted                                                               |
| PostgreSQL   | `pg_data` volume         | The system of record: projects, links, users, purchases | `pg_dump`                                                                                 |
| ClickHouse   | `clickhouse_data` volume | Events and analytics                                    | ClickHouse `BACKUP`                                                                       |
| Uploads      | `storage` volume         | Uploaded images and exports                             | `tar` of `/app/storage`. Back up the bucket instead when `ACTIVE_STORAGE_SERVICE=amazon`. |
| Redis        | `redis_data` volume      | Queues and buffered events                              | Optional. Only events not yet processed are at risk.                                      |
| Certificates | `caddy_data` volume      | Certificates issued by the standalone proxy             | Optional. Caddy issues them again.                                                        |

A disk or volume snapshot of a running database is not a reliable database backup. Use the dump commands below.

## Backup script

Save this as `backup.sh` in the install directory. It writes a timestamped folder under `backups/` and deletes folders older than 14 days.

```bash
#!/usr/bin/env bash
# Back up Grovs: .env, PostgreSQL, ClickHouse and uploads. Run from the install directory.
set -euo pipefail
umask 077
cd "$(dirname "$0")"
STAMP="$(date +%Y%m%d-%H%M%S)"
DEST="backups/$STAMP"
mkdir -p "$DEST"

cp .env "$DEST/env"
docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$DEST/postgres.dump"
docker compose exec -T clickhouse clickhouse-client -q "BACKUP DATABASE grovs_production TO File('$STAMP.zip')"
docker compose cp "clickhouse:/var/lib/clickhouse/backups/$STAMP.zip" "$DEST/clickhouse.zip"
docker compose exec -T clickhouse rm "/var/lib/clickhouse/backups/$STAMP.zip"
docker compose exec -T web tar -C /app/storage -czf - . > "$DEST/storage.tar.gz"

find backups -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +
echo "Backup written to $DEST"
```

```bash
chmod 700 backup.sh
./backup.sh
```

All three dumps run while Grovs is serving traffic. ClickHouse writes its archive inside its own volume before the copy, so keep free disk space for one ClickHouse backup. For a local trial, run `export COMPOSE_FILE=docker-compose.yml:docker-compose.local.yml` first.

## Schedule backups

Run the script daily from root's crontab, then copy the folder off the server. A backup on the same disk does not survive the loss of that disk.

```bash
0 3 * * * /opt/grovs/backup.sh >> /var/log/grovs-backup.log 2>&1
30 3 * * * rsync -a /opt/grovs/backups/ backup-user@backup.example.com:grovs-backups/
```

Replace the `rsync` target with your own storage. Each folder holds a copy of `.env`, so keep the destination private and encrypted.

## Restore

Restore onto a new server, or onto the same server to undo a failed change. The restored `.env` must be the one saved with the backup: it pins `GROVS_VERSION` and holds the keys for the encrypted data.

### 1. Prepare the install directory

On a new server, get the stack files and put the saved `.env` in place. Do not run `setup.sh`; it would generate new secrets for an empty directory.

```bash
git clone https://github.com/grovs-io/self-host.git grovs && cd grovs
cp /path/to/backup/env .env
chmod 600 .env
```

On the existing server, stop the application instead:

```bash
docker compose --profile standalone stop dashboard web worker-1 worker-2
```

### 2. Start the data stores

Start only the databases, then set `B` to the backup folder you are restoring:

```bash
docker compose up -d --wait postgres redis clickhouse
B=/path/to/backup
```

### 3. Restore PostgreSQL

Recreate the database empty, then load the dump:

```bash
docker compose exec -T postgres sh -c 'dropdb -U "$POSTGRES_USER" --force "$POSTGRES_DB" && createdb -U "$POSTGRES_USER" "$POSTGRES_DB"'
docker compose exec -T postgres sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --no-owner --exit-on-error' < "$B/postgres.dump"
```

### 4. Restore ClickHouse

ClickHouse reads backups from a folder inside its volume. Create it as the `clickhouse` user, copy the archive in, then replace the database:

```bash
docker compose exec -T -u clickhouse clickhouse mkdir -p /var/lib/clickhouse/backups
docker compose cp "$B/clickhouse.zip" clickhouse:/var/lib/clickhouse/backups/restore.zip
docker compose exec -T clickhouse clickhouse-client -q "DROP DATABASE IF EXISTS grovs_production SYNC"
docker compose exec -T clickhouse clickhouse-client -q "RESTORE DATABASE grovs_production FROM File('restore.zip')"
docker compose exec -T clickhouse rm /var/lib/clickhouse/backups/restore.zip
```

### 5. Restore uploads

```bash
docker compose run --rm --no-deps -T web tar -C /app/storage -xzf - < "$B/storage.tar.gz"
```

### 6. Start Grovs

```bash
docker compose --profile standalone up -d
docker compose --profile standalone ps -a
```

`migrate` runs again and exits with code 0 against the restored data. On a new server, point DNS at it before you send traffic.

## Test your restore

Restore the latest backup onto a separate server or VM on a regular schedule, then check:

1. Every container is `running`, and `migrate` exited with code 0.
2. You can log in with an existing dashboard account. The bootstrap password in `.env` only works if nobody changed it.
3. Your projects and links are there, and a link opens.
4. Analytics show the history you expect.
5. Uploaded images load.

Use a test domain for the test server so it does not take traffic from production.

## Platforms

Railway, Render, Easypanel and CapRover use managed databases or object storage. They need the same coverage: PostgreSQL, ClickHouse, the bucket and the environment secrets. Use each platform's backup tools, and keep a private copy of the environment.

## Before you remove a server

Back up the databases, uploads and `.env` outside that server. Deleting a server or disk can destroy the data. Check your provider's console for retained disks, IPs and backups that keep billing.
