# Backup and restore

> What to back up, how to automate it, and how to prove a restore actually works.

> For the complete documentation index, see [llms.txt](https://helpdesk.orangescrum.com/llms.txt).

Source: https://helpdesk.orangescrum.com/guide/community/operate/backup-and-restore

---
A Community install holds two things you cannot regenerate: the **PostgreSQL
database** and the **attachment files**. Back up both, or your restore will be
half a system.

> **An untested backup is not a backup**
>
> Restore into a scratch instance at least once. Most backup failures are
> discovered during the first real restore, which is the worst time.

## What to back up

| Item | Docker location | Manual location |
| --- | --- | --- |
| Database | `orangescrum-postgres-data` volume | Your PostgreSQL data directory |
| Attachments | `orangescrum-app-files` volume | `webroot/files` |
| Configuration | `orangescrum-app-config` volume | `config/` |

The config volume is small but holds the generated database configuration —
including it saves a fiddly manual step on restore.

## Backing up (Docker)

```bash
#!/usr/bin/env bash
set -euo pipefail

STAMP=$(date +%F-%H%M)
DEST=/backups/orangescrum
mkdir -p "$DEST"

# Database — custom format compresses and restores selectively.
docker compose exec -T orangescrum-postgres \
  pg_dump -U orangescrum -Fc orangescrum > "$DEST/db-$STAMP.dump"

# Attachments.
docker run --rm \
  -v orangescrum_orangescrum-app-files:/files:ro \
  -v "$DEST":/backup alpine \
  tar czf "/backup/files-$STAMP.tar.gz" -C /files .

# Configuration.
docker run --rm \
  -v orangescrum_orangescrum-app-config:/config:ro \
  -v "$DEST":/backup alpine \
  tar czf "/backup/config-$STAMP.tar.gz" -C /config .

# Keep 30 days.
find "$DEST" -type f -mtime +30 -delete
```

> **Check your volume prefix**
>
> Compose prefixes volume names with the project name — `orangescrum-oss` in the
> shipped file. Confirm with `docker volume ls` and adjust the script.

Schedule it nightly:

```
0 2 * * * /usr/local/bin/orangescrum-backup.sh >> /var/log/orangescrum-backup.log 2>&1
```

## Backing up (manual install)

```bash
pg_dump -U orangescrum -Fc orangescrum > db-$(date +%F).dump
tar czf files-$(date +%F).tar.gz -C /var/www/orangescrum/webroot files
tar czf config-$(date +%F).tar.gz -C /var/www/orangescrum config
```

## Restoring

**Stop the application**

```bash
docker compose stop orangescrum-app
```

    Restoring underneath a running app produces inconsistent state.

**Restore the database**

```bash
docker compose exec -T orangescrum-postgres \
  dropdb -U orangescrum --if-exists orangescrum

docker compose exec -T orangescrum-postgres \
  createdb -U orangescrum orangescrum

docker compose exec -T orangescrum-postgres \
  pg_restore -U orangescrum -d orangescrum < /backups/orangescrum/db-2026-08-01-0200.dump
```

**Restore attachments**

```bash
docker run --rm \
  -v orangescrum_orangescrum-app-files:/files \
  -v /backups/orangescrum:/backup alpine \
  sh -c "rm -rf /files/* && tar xzf /backup/files-2026-08-01-0200.tar.gz -C /files"
```

**Start and clear caches**

```bash
docker compose start orangescrum-app
docker compose exec orangescrum-app php bin/cake.php cache clear_all
```

**Verify**

    Sign in, open a project, open a task with an attachment, and download it.
    A database-only restore looks fine until someone clicks a file.

## Testing the restore

Quarterly, restore into a throwaway instance on a different port:

```bash
docker compose -p orangescrum-test up -d
```

Then run the restore against that project name. It costs an hour and tells you
whether your backups are real.

## What to watch

- **Backup size**: A sudden drop usually means the dump failed and wrote a near-empty file. Alert on it.

- **Attachment volume growth**: This is what fills the disk. Track it before it stops the app.

- **Off-machine copies**: A backup on the same host does not survive losing the host.

- **Encryption**: Dumps contain everything. Encrypt them at rest.

- [Troubleshooting](https://helpdesk.orangescrum.com/guide/community/operate/troubleshooting): Common failures and how to read the logs.
