Backups and Restore
CourseMaker holds two pieces of state on the server:
- The Postgres volume (
pgdata) — users, courses, page-builder JSON, theme, encrypted credentials. - The
ENCRYPTION_KEYenvironment value — required to decrypt the credentials in (1).
Both must be backed up together. A DB dump without the key is a brick.
Video files live in Bunny Stream, not on the server — Bunny handles their durability separately.
Daily DB dump
Cron (root crontab):
0 3 * * * docker compose -f /opt/coursemaker/docker-compose.yml \
exec -T postgres pg_dump -U coursemaker -d coursemaker \
| gzip > /var/backups/coursemaker/$(date +\%Y\%m\%d).sql.gz
Retain 30 days, ship offsite (S3, B2, Borg, restic — your call).
Backing up the encryption key
Either:
-
Paste it into the client's password manager / secrets vault alongside their root credentials, or
-
Encrypt it with
age/gpgand store next to the DB dumps:echo -n "$ENCRYPTION_KEY" | age -r <recipient> > key.age
Test the recovery path at least once a year. A backup you cannot restore is not a backup.
Restoring
On a fresh host:
git clone https://github.com/<your-org>/coursemaker.git /opt/coursemaker
cd /opt/coursemaker
cp .env.example .env
# Paste the ORIGINAL ENCRYPTION_KEY into .env. Generate the
# others fresh.
docker compose up -d --build
docker compose stop app
zcat /path/to/backup.sql.gz | docker compose exec -T postgres \
psql -U coursemaker -d coursemaker
docker compose start app
Visit the URL. Sign in with an existing admin account. Open
/admin/settings and confirm the saved Stripe / Bunny / SMTP keys
load — that's the proof the encryption key matches.
What if you lost the encryption key?
The encrypted credentials in app_settings are unrecoverable. The
DB structurally is fine — you keep all users, courses, and lessons —
but the client must re-paste every Stripe / Bunny / SMTP value into
/admin/settings. There is no other path.