Quickstart
By the end of this page you will have backed up a PostgreSQL database, verified the backup's integrity, and restored it into a second database; with everything running on your own machine and nothing left behind.
Budget about fifteen minutes.
What you need
- Sentinel installed: see Installation.
psqlandpg_dumpon yourPATH. Both ship with the PostgreSQL client package.- Docker, to run a throwaway PostgreSQL. If you already have a database you can afford to experiment against, use that instead and adjust the connection details as you go.
Step 1: Start a throwaway database
docker run --name sentinel-quickstart \
-e POSTGRES_PASSWORD=quickstart \
-e POSTGRES_DB=quickstart \
-p 5432:5432 -d postgres:17
Give it a few seconds, then put some data in it:
export PGPASSWORD=quickstart
psql -h 127.0.0.1 -U postgres -d quickstart -c \
"CREATE TABLE widgets (id serial PRIMARY KEY, name text);
INSERT INTO widgets (name) VALUES ('alpha'), ('beta'), ('gamma');"
You should see INSERT 0 3.
Step 2: Write a configuration file
Everything Sentinel does is driven by one YAML file. Create sentinel.yaml:
version: "1.0"
log_format: text
defaults:
storage:
type: local
local_path: ./backups
retention:
keep_last: 5
databases:
quickstart:
type: postgres
host: 127.0.0.1
port: 5432
username: postgres
password_env: PGPASSWORD
database: quickstart
restores:
quickstart-check:
type: postgres
enabled: true
host: 127.0.0.1
port: 5432
username: postgres
password_env: PGPASSWORD
database: quickstart_restored
schedule: "0 3 * * 0"
backup_source:
type: local
local_path: ./backups
backup_path: "SENTINEL_*.sql"
use_latest_match: true
verify_after_restore: trueThe key is accepted by the configuration, but no post-restore verification handler is wired in the
current release. Setting it makes the restore fail with
verification handler is required for restore mode "full" after the data has already been
written, so the run is recorded as a failure even though the restore succeeded. Step 7 below verifies
the result by querying the database, which is what you want here anyway.
Two things in there are worth understanding now, because they are Sentinel-wide rules rather than quickstart shortcuts:
password_env: PGPASSWORDnames an environment variable; it is not the password. Sentinel never accepts a password as a configuration value or a command-line argument, because both end up in shell history, process listings, and version control.use_latest_match: truemakesbackup_patha pattern. Backups are namedSENTINEL_<timestamp>.sqlby default, soSENTINEL_*.sqlselects the most recent one without you having to know its filename.
Check the file before going further:
sentinel config validate --config sentinel.yaml
You should see configuration is valid, along with a warning that TLS is not configured. That
warning is correct and expected here; you are connecting to a local container over a loopback
address. On a real database, configure TLS.
Validation resolves password_env immediately. If PGPASSWORD is not exported, Sentinel fails with
environment variable 'PGPASSWORD' is not set rather than deferring the problem to backup time.
Step 3: Take a backup
sentinel backup --config sentinel.yaml
Sentinel connects, runs pg_dump, writes the artifact to ./backups/, records a SHA-256 manifest
beside it, and logs the run to its history database.
Confirm the artifact exists:
ls backups/
You should see a file named SENTINEL_<timestamp>.sql.
Step 4: Check it in the history
Every execution is recorded, whether it succeeded or failed:
sentinel monitor list --config sentinel.yaml
You should see one row for the quickstart job with a success status. Note its backup ID; the next
step uses it.
Step 5: Verify the backup's integrity
A backup you have not verified is a hope, not a backup. Sentinel hashes every artifact at write time and can re-check it later:
sentinel backup verify --all --config sentinel.yaml
This re-reads each stored artifact, recomputes its SHA-256, and compares it against the manifest recorded when the backup was taken. A mismatch means the file changed after Sentinel wrote it.
To verify one specific backup instead, pass its ID:
sentinel backup verify <backup-id> --config sentinel.yaml
Step 6: Restore into a second database
Restoring over your source database would prove nothing and destroy your data. Create a separate empty target:
psql -h 127.0.0.1 -U postgres -d postgres -c "CREATE DATABASE quickstart_restored;"
The quickstart-check job in your configuration already points at it. Rehearse first; a dry run
resolves the source and reports what would happen, without touching the target:
sentinel restore dry-run quickstart-check --config sentinel.yaml
Then run it for real:
sentinel restore run quickstart-check --config sentinel.yaml
Step 7: Confirm the data came back
psql -h 127.0.0.1 -U postgres -d quickstart_restored -c "SELECT * FROM widgets;"
You should see the three rows, alpha, beta, gamma, that you inserted in Step 1.
That is the whole loop: back up, verify, restore, confirm.
Clean up
docker rm -f sentinel-quickstart
rm -rf backups sentinel.yaml
What just happened
You ran the same code path Sentinel uses in production. The difference between this and a real deployment is configuration, not mechanism:
- The backup ran once because you asked it to. In production you would add a
scheduleand runsentinel schedule startto keep a cron loop going. - The artifact stayed on local disk. Storage is a configuration choice; S3, Google Cloud Storage, Google Drive, and Azure Blob are all supported.
- The dump was a full backup. For large databases, incremental backup uses PostgreSQL's write-ahead log so daily backups do not mean daily full dumps.
- The artifact was written in plaintext. Encryption is opt-in and off by default.
- You verified manually. Integrity checks can run on a schedule instead.
Next
- How Sentinel fits together: the mental model behind what you just ran.
{/* sources: README.md §Quick start, internal/config/types.go, internal/config/restore_types.go, internal/cli/backup.go, internal/cli/restore.go, internal/cli/config.go, internal/utils/default.go, docs/runbooks/run-backup-from-config.md */}