Objective. Students will write and validate a production-quality Bash script that performs scheduled PostgreSQL logical backups using pg_dump, manages backup retention, and executes a test restore into a staging database.
This lesson, its worked examples and its practice questions has not yet been verified by an industry-certified professional or by a certification or approval body. It is a study draft and may contain errors. Do not use it as instructions for work on a job site or on live equipment. Always follow your instructor, your supervising professional, and current official references and regulations.
VoTechWorks program approval and accreditation are still in progress. Credentials are issued only by the certifying bodies named in each program, after their own exams.
Manual backups are error-prone and easy to forget. A production database must be backed up on a predictable schedule, with old backups rotated out automatically and every backup verified by a test restore. Bash scripting ties these steps together and lets you schedule the whole workflow with cron — a core skill tested on the EDB PostgreSQL CE exam and directly relevant to Oracle DBA CA and Microsoft DP-300 backup/recovery objectives.
`pg_dump` creates a logical backup of a single PostgreSQL database. A logical backup captures the SQL needed to recreate all objects and data; it is not a binary copy of the data directory. This makes it portable across PostgreSQL versions (with some restrictions) and easy to restore into a differently-named database.
Key flags you will use in this lab:
For this lab we use plain-text format piped through `gzip` so the restore path with `gunzip | psql` stays simple and visible.
Every backup file should include the date and time it was created. A reliable pattern is:
```bash
FILENAME="${PGDATABASE}_$(date +%Y%m%d_%H%M%S).sql.gz"
```
`date +%Y%m%d_%H%M%S` expands to something like `20240915_023001`, which sorts lexicographically in chronological order — useful when listing files.
Retention management removes backups older than a chosen number of days so disk space does not fill up. The POSIX `find` utility handles this cleanly:
```bash
find "${BACKUP_DIR}" -name "*.sql.gz" -mtime +"${RETENTION_DAYS}" -delete
```
`-mtime +N` matches files whose modification time is more than N days ago. Always test `find` commands without `-delete` first to confirm the file list is what you expect before deletions occur.
Hardcoding a password directly inside a script — for example `PGPASSWORD=secret` — is a serious security violation. Anyone who can read the file, view process environment variables (`/proc/<pid>/environ`), or access version control history can recover the credential. This violates DAMA CDMP data-governance principles around access control and least-privilege, and it typically violates organizational security policy.
The correct approach for non-interactive scripts is the `.pgpass` file. PostgreSQL reads `~/.pgpass` (or the path in `$PGPASSFILE`) when it needs a password. The file format is one record per line:
```
hostname:port:database:username:password
```
Wildcards (`*`) are allowed in any field. Critically, PostgreSQL ignores `.pgpass` unless its permissions are exactly `0600` (owner read/write only). Set this with:
```bash
chmod 0600 ~/.pgpass
```
In your script, export `PGPASSFILE` to point to the file, or rely on the default `~/.pgpass`. Never commit `.pgpass` to version control.
A production-quality script should:
1. Read all site-specific values from environment variables (`PGHOST`, `PGPORT`, `PGDATABASE`, `PGPASSFILE`, `BACKUP_DIR`, `RETENTION_DAYS`) — no hardcoded values.
2. Validate that required variables are set and exit with a non-zero code if any are missing.
3. Create the backup directory if it does not exist.
4. Run `pg_dump` and pipe output through `gzip`, redirecting to the timestamped file.
5. Check the exit code of each command and log success or failure.
6. Run the `find` retention cleanup after a successful backup.
Always add `set -euo pipefail` at the top of your script. `-e` exits on error, `-u` treats unset variables as errors, and `-o pipefail` catches failures in any stage of a pipeline — essential when `pg_dump | gzip` is your backup command.
A backup you have never tested is not a backup. The restore procedure for a plain-text gzip dump is:
```bash
gunzip -c "${BACKUP_FILE}" | psql -h "${PGHOST}" -p "${PGPORT}" \
-U "${PGUSER}" -d restore_test
```
`-c` tells `gunzip` to write to stdout rather than a file, keeping the pipeline intact. The target database `restore_test` must already exist (create it with `createdb restore_test` beforehand).
After the restore, run a row-count query on a known table to verify data integrity:
```bash
psql -d restore_test -c "SELECT COUNT(*) FROM orders;"
```
Compare the result to the same query run against the source database. Document both counts, whether they match, and the timestamp in your lab journal.
Database administration carries real risk of data loss. Apply these habits:
Answer each question, then check it. Nothing is recorded; this is your own practice.
A free tech program — complimentary, as our commitment to get America working. Help us build VoTechWorks. Tell us what works, what doesn’t, and what you want to learn.