DocumentationOperations

Rust · 0.1.0

Backup and restore

Protect a consistent set of database, files and configuration, then test it.

Back up the whole installation

Record the application version/source, runtime configuration and data locations. Back up the database using the engine's consistent backup method. Include local attachment directories or S3 objects, and separately protect deployment settings and the secrets needed for recovery. Store copies away from the only application host. A personal memo export is useful but is not a complete installation backup.

SQLite consistency

Do not copy only the main database file while a writer is active and assume it is complete. Use SQLite's supported backup mechanism or stop the application cleanly and capture the complete database state. If you stop the service, keep it stopped until all related file snapshots are taken. Test the procedure on non-production data before depending on it.

Restore into isolation first

Restore to a separate data directory or database and an unexposed listener. Avoid sending webhooks or email from a test restore. Check sign-in, memo counts, sample text, dates, attachments and access boundaries. Only switch users to the restored instance after verification. A backup job finishing without errors is not proof that the backup can be restored.

Offline backup for the default Docker SQLite setup

This recipe applies only when the container is named memos, the image is memos:local, and both the SQLite database and local attachments are inside the memos-data volume from Getting started. It deliberately stops writes. It does not back up a remote database, external attachment paths, S3 objects or separately mounted secrets. Record those separately if you use them. Ensure adequate free disk space and prepare a maintenance window. The helper uses the already-built image, bypasses its application entrypoint and has no network.

set -eu
BACKUP_DIR="$PWD/backups/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
docker stop --time 60 memos
test "$(docker inspect memos --format '{{.State.Running}}')" = false
docker run --rm --network none --entrypoint /bin/sh \
  -v memos-data:/source:ro \
  --mount type=bind,src="$BACKUP_DIR",dst=/backup   memos:local -c 'tar -C /source -czf /backup/memos-data.tgz .'
tar -tzf "$BACKUP_DIR/memos-data.tgz"
# Restart only after the archive command and listing succeed.
docker start memos

Restore into a new unused volume

Keep BACKUP_DIR set to the archive directory from the previous step. Docker generates a new unused volume name below; record the printed name for later cleanup/review. Use this new destination volume; never extract over your production volume. The following restores without starting the application, then runs the binary’s read-only SQLite inspect command with networking disabled. Expected output includes readOnly: true and quickCheck: "ok". A missing database or failed integrity check means the restore has not passed; keep the original installation untouched.

set -eu
: "${BACKUP_DIR:?Set BACKUP_DIR to the completed backup directory}"
RESTORE_VOLUME="$(docker volume create)"
printf 'Restore volume: %s\n' "$RESTORE_VOLUME"
docker run --rm --network none --entrypoint /bin/sh \
  -v "$RESTORE_VOLUME:/restore" \
  --mount type=bind,src="$BACKUP_DIR",dst=/backup,readonly   memos:local -c 'tar -C /restore -xzf /backup/memos-data.tgz'
docker run --rm --network none \
  --entrypoint /usr/local/memos/memos \
  -v "$RESTORE_VOLUME:/restore"   memos:local inspect --database /restore/memos_prod.db

Start the restored application without outbound networking

After inspect passes, keep RESTORE_VOLUME set to the new volume and start the matching image with --network none. Do not publish any port. The packaged image includes wget for a health check executed inside the container. If the first check occurs before startup finishes, read the logs and repeat it. This tests actual application startup while preventing restored webhooks, email or provider traffic from reaching other hosts. Stop the test container after the check; keep its volume for review. It cannot verify SSO, S3 or browser sign-in while networking is disabled; complete those in a separately controlled test network before calling the recovery fully accepted.

docker run -d --name memos-restore-check --network none \
  -e MEMOS_DRIVER=sqlite -e MEMOS_PORT=5230 \
  -v "$RESTORE_VOLUME:/var/opt/memos" memos:local
docker logs --tail 100 memos-restore-check
docker exec memos-restore-check wget -qO- http://127.0.0.1:5230/healthz
docker stop --time 60 memos-restore-check

Finish the recovery rehearsal

Integrity checking does not validate every memo or attachment. In a separate test environment with outbound delivery blocked, start the restored data using the matching application build and configuration, then compare user count, a known memo’s text/date, one local attachment and private/space access. Keep the test listener unexposed. Only after those checks should you plan a production cutover. Preserve the archive, configuration record and matching image together and copy the backup away from this host. For named-volume behavior, see the Docker volume guide.