Database
PostgreSQL holds the catalogue of every backup, the job queue and the audit log. Backed-up content lives in the repositories, not in the database, but without the database Restow does not know what is in them: a full database disk stops the database, and with it every backup. This page says where the data lives, how Restow watches the free space, how to move the database to another disk and what the installer does to protect a moved database.
Where the data lives
Section titled “Where the data lives”The postgres service takes its data directory from two lines of .env (the docker-compose.yml of release 0.4.0 and later):
.env |
Data directory |
|---|---|
| both lines empty (the default) | the Docker volume pgdata (named restow_pgdata in the Compose project restow), in Docker’s data directory, usually on the system disk |
RESTOW_POSTGRES_DATA_TYPE=bind and RESTOW_POSTGRES_DATA=/mnt/…/restow-postgres-<time> |
that directory on the host |
.env carries the location, not a Compose override, because .env is the one file every way of running the stack reads: the updater never changes the compose file, and install.sh replaces docker-compose.yml with the file of the release when it runs again and calls Compose with -f, which ignores docker-compose.override.yml. The updater writes the two lines when it moves the database; you only write them yourself when you move the database by hand (below). Both variables are optional and listed in Environment variables.
The service mounts the directory with create_host_path: false. When the disk under the directory is not mounted (a volume that did not attach after a reboot), Docker refuses to start PostgreSQL instead of creating an empty directory that PostgreSQL would initialize as a new, empty database. A move puts the data into a subdirectory restow-postgres-<yyyymmdd-hhmmss> of the directory you name, so even a mount point that is empty while unmounted gives a missing path, not an empty one.
Disk space
Section titled “Disk space”Installation → Database shows the location, the size of the data directory, its file system and the free space under it, the free space under the local default repository (STORAGE_LOCAL_PATH, or a local default saved under Installation → Default repository & network shares) and what the next update needs on disk (Updates). Reading the database’s disk goes through the updater; without it only the repository’s disk is watched and the location cannot be read or changed here.
Low disk space warning
Section titled “Low disk space warning”A disk counts as low when less than a tenth of it is free, and always below 5 GiB. Every 15 minutes the api checks the disk under the database and the disk under the local default repository, and since 0.4.5 also the server’s system disk (/ as the api container sees it, the disk under Docker’s data directory), whose free space it keeps once an hour for two weeks for the trend the dashboard shows when a repository lies on that disk (Where backups land). When one becomes low, Restow raises the installation event Low disk space (installation.disk_low) once:
- a notice in the bell of the provider administrators, and
- an e-mail or webhook from every alert rule that lists “Low disk space”. Only provider administrators can choose this event, see Notifications and reports.
It is raised again only after the disk recovered to a fifth above the line in between. The page shows a Low mark on the disk and says what to do: free up space, enlarge the disk, or move the database to another disk.
Moving the database
Section titled “Moving the database”Installation → Database → Move the database to another disk. It needs the opt-in updater and the docker-compose.yml of 0.4.0 or later (if the file does not read the location from .env, the page says so and offers the installer command; see the installer guard). Checking a directory and moving are for the provider owner. Moving and removing an old copy need a sign-in from the last minutes. Every request is written to the audit log: database.move.requested, and from the updater database.move.started, database.move.succeeded, database.move.failed, database.old_copy.removal_requested and database.old_copy.removed.
- Attach the new disk and mount it on the host (for example at
/mnt/volume-1, with an entry in/etc/fstab), and create a directory on it, for example/mnt/volume-1/restow-db. - Enter the directory under Directory on the host and choose Check directory. Nothing changes; the updater checks that:
- the path is a plain absolute path of letters, digits and
. _ - + @, and not a system directory (/etc,/usr,/var/lib/docker,/tmpand the like); - the directory exists on the host and is writable;
- it is on another file system than the data now;
- the file system suits PostgreSQL: ext4, XFS, Btrfs or ZFS. NFS, SMB/CIFS, FUSE, tmpfs, overlay and anything else are refused;
- it has room for the data directory plus a tenth of it, at least 2 GiB;
- the database has no tablespaces outside its directory;
.envis writable, and Compose takes the location from.envand resolves it to where the database runs now.
- the path is a plain absolute path of letters, digits and
- Choose Move database…. The dialog names the downtime and the size; type the directory again to confirm. A move is refused while an update is announced or running, and an update, a restart of a service and a start of the mounter are refused while a move runs.
Restow is unavailable from step 1 to step 8 below. The copy and its comparison read the data about three times, so plan several minutes per 10 GB on ordinary disks. Running backups are interrupted and continue after the move.
| Step | What happens | What is checked |
|---|---|---|
| 1. Stop api, worker and scheduler | The worker checkpoints running jobs; the edge keeps serving the maintenance page. | |
| 2. Stop the database cleanly | PostgreSQL stops (up to 120 seconds). | pg_controldata must report shut down. |
| 3. Copy the data directory | <directory>/restow-postgres-<time> is created and the data copied with owner, permissions and times, in a helper container of the postgres image, with the old data mounted read-only. |
|
| 4. Compare the copy with the original | Both trees are listed side by side. | The same entries, types, sizes, permissions, owners and the SHA-256 of every file. |
| 5. Test start on the copy | PostgreSQL starts on the copy in an isolated container (no network, as the data’s owner). | It must answer with the number of applied migrations read before the stop, and stop cleanly again. |
| 6. Switch | RESTOW_POSTGRES_DATA_TYPE=bind and RESTOW_POSTGRES_DATA=<copy> go into .env (the previous lines are kept). The old copy gets a file RESTOW-MOVED.txt, and its PG_VERSION is renamed to PG_VERSION.restow-moved. |
Compose must resolve the new location. |
| 7. Start the database from the new directory | PostgreSQL is recreated on the new directory. | Healthy, running from the new directory, the same migration count. |
| 8. Start api, worker and scheduler | The api first, then the others. | The api answers ready. |
If a step fails, Restow goes back by itself. The updater stops what it started, puts the two lines of .env back byte for byte, renames PG_VERSION of the old copy back, starts PostgreSQL on the old location (it must run from there and show the migration count of before) and starts the application. The move then ends Rolled back, and the database is unchanged. An incomplete copy is removed. A copy the database had already run from (a failure in step 7 or 8) is kept and named on the page, because it may hold a few minutes of changes. If the updater itself is restarted in the middle of a move, it takes the same way back when it starts again. When the way back fails as well, the move ends Needs attention, and PostgreSQL is left stopped rather than started on a location that might be the wrong one (see below).
The old copy
Section titled “The old copy”The move never deletes the old copy. Its renamed PG_VERSION makes PostgreSQL refuse to start on it: PostgreSQL would otherwise run on stale data if .env were changed back or an old compose file were used, and the container then fails with “directory exists but is not empty”. Once the database runs well from the new disk, remove the old copy with Remove old copy (the provider owner, with a recent sign-in). The updater deletes its content and keeps RESTOW-MOVED.txt, so the directory can never be mistaken for an empty database. The volume restow_pgdata itself stays, empty but for that note.
Going back to the old copy after a successful move is not offered: it is a snapshot of the moment of the move, and every backup recorded since would be missing from it. To return to the system disk, move the database again, to a directory there.
When a move needs attention
Section titled “When a move needs attention”The page shows which step failed and the updater’s log. The data is in one of two places, both intact: the old location (until step 6 nothing touched it) and, from step 3 on, the copy. Check .env, then:
cd /opt/restowgrep RESTOW_POSTGRES_DATA .env # where Compose will start PostgreSQL# the old location, if it is the volume:docker run --rm -v restow_pgdata:/old alpine ls /old# a quarantined old copy has PG_VERSION.restow-moved instead of PG_VERSION; to use it again:docker run --rm -v restow_pgdata:/old alpine sh -c 'cd /old && mv PG_VERSION.restow-moved PG_VERSION && rm -f RESTOW-MOVED.txt'# with both lines of .env empty (old location) or pointing at the copy:docker compose up -d postgres && docker compose up -dUse the location the last successful state ran from: the old one when the move ended before step 7, the copy when it failed after the database had run from it (the page names it as the kept copy).
Moving by hand
Section titled “Moving by hand”Without the updater, or with an older compose file, do the same steps by hand, as root on the host, in the installation directory. The compose file must be the one of release 0.4.0 or later, because only that file reads the location from .env:
cd /opt/restowdocker compose stop api worker schedulerdocker compose stop -t 120 postgresNEW=/mnt/volume-1/restow-db/restow-postgres-$(date -u +%Y%m%d-%H%M%S)mkdir "$NEW"# copy with owner and permissions (the postgres image's own cp; the old data read-only)docker run --rm -v restow_pgdata:/from:ro -v "$NEW":/to --entrypoint sh postgres:16-alpine \ -c 'cp -a /from/. /to/ && chown "$(stat -c %u:%g /from)" /to && chmod "$(stat -c %a /from)" /to'# compare (both lines must be identical)docker run --rm -v restow_pgdata:/a:ro -v "$NEW":/b:ro --entrypoint sh postgres:16-alpine \ -c 'for d in /a /b; do (cd $d && find . -type f -exec sha256sum {} + | LC_ALL=C sort -k2 | sha256sum); done'# point .env at the copyprintf 'RESTOW_POSTGRES_DATA_TYPE=bind\nRESTOW_POSTGRES_DATA=%s\n' "$NEW" >> .envdocker compose config --format json | grep -c "$NEW" # must be at least 1# keep the old copy from starting by mistakedocker run --rm -v restow_pgdata:/old alpine sh -c 'echo "moved to '"$NEW"'" > /old/RESTOW-MOVED.txt && mv /old/PG_VERSION /old/PG_VERSION.restow-moved'docker compose up -d postgres && docker compose up -dRemove empty RESTOW_POSTGRES_DATA_TYPE= and RESTOW_POSTGRES_DATA= lines further up in .env first (the last assignment wins, but one line each is clearer). To undo before the application has started, empty the two lines again, rename PG_VERSION.restow-moved back and start the stack.
The installer guard
Section titled “The installer guard”Once the database was moved to another disk, install.sh refuses to put in place or start a docker-compose.yml that does not read RESTOW_POSTGRES_DATA from .env. It stops with exit code 8 and changes nothing, so that an older release file cannot start the stack without its database (PostgreSQL would start on an empty or old directory). The message names the location and asks for the docker-compose.yml of release 0.4.0 or later. The 0.3.6 file does not read the setting: after a database move, keep a compose file that reads RESTOW_POSTGRES_DATA, also when you roll back an update. See Running it again and Upgrading to 0.4.0.
Kept database dumps
Section titled “Kept database dumps”Before every update the updater writes a database dump into its volume (restow-updater, /state/dumps) and keeps the newest three, plus the one a run that needs attention depends on. Since 0.4.0 the Updates tab of Installation → Updates lists them under Database backups, with their size and creation time, and the provider owner can download one (a one-time link that is valid for a minute and tied to you and that file, after a recent sign-in, recorded in the audit log as update.dump.downloaded) or delete it (update.dump.deleted). The dumps sit on Docker’s disk: delete old ones there when room is short.
The disk under the dumps counts in the space an update needs: see Updates. To restore a dump, see Rolling back by hand.
Not covered
Section titled “Not covered”- Tablespaces, a
PGDATAbelow the mount point and network file systems are refused rather than handled. - Going back to an old copy after a successful move (move the database again instead).