All Geo-MLOps state lives in two places: Postgres (the DB shared by the app and MLflow) and RustFS (datasets, image layers, model artifacts, build logs, edge uploads). Both exist only in Docker volumes, so anything deleted by mistake — such as a permanently deleted MLflow experiment or a deleted tenant — cannot be recovered without a backup. Compose does not take backups, so install the backup script and systemd timer that ship with the source.

What is backed up, and how

TargetMethodRetention
Postgrespg_dump -Fc inside the container → BACKUP_DIR/postgres/mlflow-<UTC-time>.dump. Each dump is read back with pg_restore --list right away; broken files are not keptDumps older than BACKUP_KEEP_DAYS (default 14 days) are deleted
RustFSaws s3 sync per bucket → BACKUP_DIR/objects/<bucket>/Additive only (no --delete). Deleted objects stay in the mirror too. Clean up by hand when space runs low
  • It runs every day at 03:30 (with a random delay of up to 15 minutes). A night's backup missed while the server was off is caught up at the next boot.
  • It runs at low priority (Nice=10, idle I/O) so it does not compete with training and requests for the disk.
  • pg_dump is the one inside the Postgres container, and the S3 copy uses an AWS CLI container. Nothing needs to be installed on the server.

Installation (once, on the server)

  1. Install the script and the unit files.

    D=/opt/geo-mlops/deploy/backup
    sudo install -m 0755 $D/backup.sh /usr/local/sbin/geo-mlops-backup
    sudo install -m 0644 $D/geo-mlops-backup.service /etc/systemd/system/
    sudo install -m 0644 $D/geo-mlops-backup.timer /etc/systemd/system/
  2. If you want anything different from the defaults, write it in /etc/geo-mlops/backup.env (the file is optional).

    sudo tee /etc/geo-mlops/backup.env >/dev/null <<'ENV'
    BACKUP_DIR=/mnt/backup/geo-mlops
    BACKUP_KEEP_DAYS=14
    ENV
  3. Enable the timer and run the first backup now to check it.

    sudo systemctl daemon-reload
    sudo systemctl enable --now geo-mlops-backup.timer
    sudo systemctl start geo-mlops-backup.service
    journalctl -u geo-mlops-backup.service -n 20      # last line "backup finished"
    systemctl list-timers geo-mlops-backup.timer      # next run time

Values you can change in backup.env

VariableDefaultMeaning
BACKUP_DIR/var/backups/geo-mlopsWhere backups accumulate
BACKUP_KEEP_DAYS14Days to keep DB dumps
BACKUP_BUCKETS(empty = all buckets)Bucket names separated by spaces
BACKUP_SKIP_OBJECTS01 backs up the DB only
PG_CONTAINER / PG_USER / PG_DBgeo-mlops-mlflow-postgres / mlflow / mlflowPostgres container and account
S3_ENDPOINT / S3_ACCESS_KEY / S3_SECRET_KEYhttp://localhost:9000 / rustfsadmin / rustfsadminRustFS connection details. If you changed the account in Compose, change these too
AWS_CLI_IMAGEamazon/aws-cli:2.34.63Image used for the S3 copy

Restore

Database

Stop the app so nothing writes to the DB, then restore the dump.

docker stop geo-mlops-app
docker exec -i geo-mlops-mlflow-postgres \
    pg_restore -U mlflow -d mlflow --clean --if-exists --no-owner \
    < /var/backups/geo-mlops/postgres/mlflow-<UTC-time>.dump
docker start geo-mlops-app

Objects

Upload one bucket (or a prefix inside it) back from the mirror. The mirror is additive only, so objects deleted after the backup time also come back — upload only the prefixes you need.

docker run --rm --network host -v /var/backups/geo-mlops/objects:/backup \
    -e AWS_ACCESS_KEY_ID=rustfsadmin -e AWS_SECRET_ACCESS_KEY=rustfsadmin \
    -e AWS_DEFAULT_REGION=us-east-1 -e AWS_REQUEST_CHECKSUM_CALCULATION=when_required \
    amazon/aws-cli:2.34.63 --endpoint-url http://localhost:9000 \
    s3 sync /backup/datasets/<tenant_id>/<dataset_id>/ s3://datasets/<tenant_id>/<dataset_id>/

Restore drill (every quarter)

A backup you do not know you can restore is not a backup. Once a quarter, check somewhere other than the production server that the dump can be read.

BACKUP_DIR=/tmp/geo-backup-drill BACKUP_BUCKETS=build-logs /usr/local/sbin/geo-mlops-backup
docker exec -i geo-mlops-mlflow-postgres pg_restore --list \
    < /tmp/geo-backup-drill/postgres/mlflow-*.dump | head

Next: Reverse proxy caveats

Written for the platform as of 2026-09-21.

© Geo-MLOps