mAPIng
MONITORED API · NG
Sign in Start free →

Backup & restore

This page covers backups for a self-run deployment. If you use the hosted
service, backups are handled for you and this page does not apply.

What is backed up

Store Backed up Contents
Postgres (control plane) Yes Organizations, users, API keys, memberships, invites, billing state
ClickHouse (metrics) No RED metrics, regenerate from live traffic

Postgres is the durability priority: identity rows (orgs, users, api keys)
exist only there, nowhere else. Billing state is a cache of Stripe and
self-heals: the hourly reconciler described in Billing &
subscriptions
re-derives it from Stripe as long as the
organization row itself is restored.

ClickHouse metrics are not backed up by default. They have bounded retention
per plan (see Plans & limits) and regenerate from live client
traffic, so losing that store degrades dashboards until they refill rather
than losing anything permanent.

Where backups go

Postgres is dumped nightly, gzipped, and pushed to an S3-compatible bucket
(S3 or self-hosted MinIO) with a 7-day retention window. Point the backup
target at storage on a different host than the deployment: a bucket on
the same box does not survive a host loss.

Verifying backups

Check at least weekly that backups are actually landing:

aws --endpoint-url "$MAPING_BACKUP_S3_PROTOCOL://$MAPING_BACKUP_S3_HOST" \
    s3 ls "s3://$MAPING_BACKUP_S3_BUCKET/$MAPING_BACKUP_S3_PATH/" --recursive | tail

Confirm the newest object is less than 24 hours old and a reasonable size,
not a near-empty file.

Running a restore drill

Restore into a scratch database and compare row counts before ever touching
production data:

# fetch the latest dump
aws --endpoint-url "$MAPING_BACKUP_S3_PROTOCOL://$MAPING_BACKUP_S3_HOST" \
    s3 cp "s3://$MAPING_BACKUP_S3_BUCKET/$MAPING_BACKUP_S3_PATH/<latest>.sql.gz" ./restore.sql.gz

# restore into a throwaway database
docker compose exec -T postgres createdb -U "$MAPING_PG_USER" maping_restore_test
gunzip -c ./restore.sql.gz | docker compose exec -T postgres \
    psql -U "$MAPING_PG_USER" -d maping_restore_test

# sanity-check row counts against production
docker compose exec -T postgres psql -U "$MAPING_PG_USER" -d maping_restore_test \
    -c "select 'orgs', count(*) from orgs union all
        select 'users', count(*) from users union all
        select 'api_keys', count(*) from api_keys;"

# drop the scratch database when done
docker compose exec -T postgres dropdb -U "$MAPING_PG_USER" maping_restore_test

A real disaster recovery restore stops the application, drops and recreates
the production database, pipes the dump into psql, and restarts. Startup
migrations are idempotent, so a restored older schema catches up
automatically on boot.

Single-host limitation

A typical self-run deployment is a single host. Backups protect durability
(your data survives a host loss) but not availability: a host failure is a
full outage until you provision a new host and replay the latest dump. If
you need faster recovery than that, run a warm standby or a managed Postgres
service with a read replica in front of your deployment.

mAPI-ng · home · source