ThreadCount Community edition

Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from a113353 on 2026-09-15. Licensed under the Functional Source License (FSL-1.1-ALv2).
This commit is contained in:
ThreadCount
2026-09-15 22:54:09 +10:00
commit a6f1059ddf
424 changed files with 53535 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
---
title: Backups and restore
section: selfhost
order: 4
summary: Nightly database dumps and photo copies from the compose file's own services and volumes, the in-app export as a second copy, and how to restore and test a restore.
role: Self-hosting admin
keywords: backup, restore, pg_dump, pg_restore, database, photos, signatures, volume, cron, export, import, disaster recovery, test restore
---
## What has to be kept
A Community instance keeps its data in two Docker volumes. Back up both, together.
| Volume | Mounted at | Holds |
|---|---|---|
| `threadcount_db` | `/var/lib/postgresql/data` in `db` | Every record: catalogue, stock, staff, issues, orders, requests, users |
| `threadcount_photos` | `/data/photos` in `app` | Signatures and damage photographs |
The names come from `docker-compose.yml`: the project is named `threadcount`, and its volumes are `db` and `photos`.
Photos are not in the database. Each one is a file at `<facility id>/<photo id>.jpg` (or `.png`) under `/data/photos`, and the database holds only that path. A database dump without the photos restores every record but none of the signatures or photographs.
## Nightly dumps
The checkout includes `docker/backup.sh`. Run it from the checkout and give it a destination directory:
```sh
cd /srv/threadcount
docker/backup.sh /srv/backups/threadcount
```
Each run writes a dated directory, for example `2026-09-15-033001`, containing:
- `threadcount.dump`: the database as a custom-format `pg_dump`, taken inside `db` as the `threadcount` user
- `photos.tgz`: the photos directory copied out of `app`
It keeps the newest 14 directories and deletes older ones. Run it nightly from cron:
```
30 3 * * * cd /srv/threadcount && docker/backup.sh /srv/backups/threadcount >> /var/log/threadcount-backup.log 2>&1
```
> **Careful** A backup on the same disk as the volumes is lost with them. Copy the backup directory to another machine.
## The export as a second copy
An admin can download the whole facility as one JSON file from `Settings Data` with Export backup. It needs no access to the server's command line, and it can be imported on another server.
| Part | In the export |
|---|---|
| All facility records | Yes |
| Photos | The newest ones only: at most 2000 photos and 40 MB of image data. The screen says how many were left out. |
| Users (admins and issuers) | No |
| Staff app logins | No. A restore keeps the existing logins and re-attaches them by staff number. |
Import backup on the same screen replaces all of that facility's data with the file's. Users are kept. See [export and backup](/docs/account/export-and-backup).
Because it leaves out older photos and users, the export is a second copy, not a replacement for the dump.
## Restore from a dump
This replaces everything on the server with the backup. Choose the backup directory first; these steps use `2026-09-15-033001`.
1. **Extract the photos.**
```sh
cd /srv/backups/threadcount/2026-09-15-033001
tar -xzf photos.tgz
```
2. **Stop the stack and remove both volumes.**
```sh
cd /srv/threadcount
docker compose down
docker volume rm threadcount_db threadcount_photos
```
3. **Start only the database, then load the dump.**
```sh
docker compose up -d db
docker compose exec -T db pg_restore -U threadcount -d threadcount --clean --if-exists < /srv/backups/threadcount/2026-09-15-033001/threadcount.dump
```
4. **Start everything.** `migrate` runs first and finds nothing to apply that the dump does not already have.
```sh
docker compose up -d
```
5. **Put the photos back and give them to the app's user.**
```sh
docker compose cp /srv/backups/threadcount/2026-09-15-033001/photos app:/data/
docker compose exec -u root app chown -R threadcount:threadcount /data/photos
```
Restore onto the release the dump was taken from, or a later one. Migrations only go forwards.
> **Careful** `docker volume rm` deletes the live data. Take a fresh backup first if the current data might still be needed.
## Test a restore
A backup that has never been restored is not proven. Restore into a second, separate stack on the same server, under another project name and port, so the live instance is untouched:
```sh
cd /srv/threadcount
APP_PORT=3100 docker compose -p tcrestore up -d db
docker compose -p tcrestore exec -T db pg_restore -U threadcount -d threadcount --clean --if-exists < /srv/backups/threadcount/2026-09-15-033001/threadcount.dump
APP_PORT=3100 docker compose -p tcrestore up -d --build
curl -fsS http://127.0.0.1:3100/api/health
```
Then check what came back:
- **Sign in.** Forward the port with `ssh -L 3100:127.0.0.1:3100` and open `http://localhost:3100` on your own machine.
- **Compare the counts.** Check the tiles on `Settings Data` against the live instance.
When you are done, remove the test stack and its volumes:
```sh
docker compose -p tcrestore down -v
```