Back to Overview

Backup & Restore

Back up and restore your Tymeslot data. Scheduling data lives in PostgreSQL and uploaded files in one Docker volume, so a pg_dump and a tar archive cover everything.

Luka Breitig, Software Engineer & AI Developer
Luka Breitig

Aggiornato il 11 marzo 2026

📋 Before You Begin

Requirements

  • Tymeslot running in Docker (on Cloudron, the platform's own backups already cover the database and data)
  • pg_dump available inside the Tymeslot container (no local install needed)
  • Root or sudo access on the host

What you will back up

  • PostgreSQL database: all your scheduling data
  • Data volume: uploaded avatars and theme backgrounds

Outcome: By the end of this guide, you will have a complete backup procedure and know how to restore from it, including how to test that your backups are valid.

📦 What to Back Up

Two things hold your Tymeslot data, and they need backing up at different intervals. Keep a copy of your .env as well: a restored database is only useful with the same DATA_ENCRYPTION_KEY (or SECRET_KEY_BASE, if you never set one), because stored calendar and video credentials cannot be decrypted without it.

🗄️

PostgreSQL database: back this up on a schedule

All users, meeting types, bookings, availability, calendar integrations and settings. Losing it means losing everything. Back it up automatically at least daily, and always before an upgrade.

🖼️

Data volume: back up weekly

Uploaded avatars and theme backgrounds, stored in the tymeslot_data Docker volume. This is less critical, since users can upload files again, so a weekly backup keeps storage costs down.

1 Back Up the Database

Run pg_dump inside the container and redirect the output to a timestamped file on the host. The --clean flag makes the dump replace existing tables when you restore it:

docker exec tymeslot su - postgres -c "pg_dump --clean --if-exists tymeslot" \
  > backup-$(date +%Y%m%d-%H%M%S).sql

# Separate database container (docker-compose.with-postgres.yml):
# docker exec tymeslot-postgres pg_dump -U tymeslot --clean --if-exists tymeslot > backup.sql

The command prints nothing, because the dump goes to the file. Confirm the backup is non-empty: ls -lh backup-*.sql.

If pg_dump fails with "database does not exist"

The database name in the command must match the one configured in your .env file. Check the value of POSTGRES_DB and use that instead:

grep POSTGRES_DB .env
# Then rerun with that database name, e.g.:
docker exec tymeslot su - postgres -c "pg_dump --clean --if-exists mydb" > backup.sql

Store Backups Off-Server

Copy backup files to a remote location such as S3, Backblaze B2, a Hetzner Storage Box or any other off-site storage. Local-only backups do not protect you against disk failure, a compromised server or an accidental docker compose down -v. Tools like rclone or restic can automate the copy.

2 Automate Database Backups

Add a cron job to run the backup every day at 02:00. Open the crontab editor with crontab -e and add the following line. It addresses the container by name, so it works from any directory, whether you started Tymeslot with docker run or Compose:

0 2 * * * docker exec tymeslot \
  su - postgres -c "pg_dump --clean --if-exists tymeslot" \
  > /backups/tymeslot-$(date +\%Y\%m\%d).sql 2>&1

The 2>&1 at the end sends errors to the same file, so a failed dump is still visible: the file holds the error message instead of SQL. Create the /backups directory first: mkdir -p /backups.

Prune old backups automatically

Add a second cron entry that deletes backups older than 30 days, so they do not fill the disk:

0 3 * * * find /backups -name 'tymeslot-*.sql' -mtime +30 -delete

3 Back Up the Data Volume

The tymeslot_data volume holds the uploads directory along with timezone data. Mount it in a temporary Alpine container and create a compressed archive:

docker run --rm \
  -v tymeslot_data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/data-$(date +%Y%m%d).tar.gz -C /data .

The archive is written to the current directory, and the command prints nothing when it succeeds. Verify: ls -lh data-*.tar.gz

Check the Volume Name

The Compose files that ship with Tymeslot pin the volume names to tymeslot_data and tymeslot_pg, the same names the docker run quick start uses. The separate-database variant, docker-compose.with-postgres.yml, names its database volume tymeslot_pgdata instead. If you wrote your own Compose file or named the volumes yourself, run docker volume ls | grep tymeslot to find the exact name.

4 Restore the Database

The database runs inside the Tymeslot container, so the application stays up during the restore. The restore runs as a single transaction: the database switches to the backup's contents in one step, and a restore that fails changes nothing. Restart the container afterwards so the application starts fresh against the restored data. With a separate database container, run the same psql command in tymeslot-postgres as psql -U tymeslot instead.

1. Note what you are replacing

docker exec tymeslot bin/tymeslot version

This prints the running version. Restore only a backup taken from this version or an older one.

2. Restore from the backup file

docker exec -i tymeslot \
  su - postgres -c "psql -v ON_ERROR_STOP=1 --single-transaction tymeslot" \
  < backup-20260101-020000.sql

psql prints a line for each statement and returns to the prompt when the restore is complete. On the first ERROR: it stops and rolls everything back. If the error mentions a deadlock, the running application held a lock at the wrong moment: run the command again.

If the restore stops with "already exists" errors

The backup was taken without --clean, so it cannot replace existing tables. Nothing was changed. Drop and recreate the database, then restore:

docker exec tymeslot su - postgres -c "psql -c 'DROP DATABASE tymeslot WITH (FORCE);'"
docker exec tymeslot su - postgres -c "psql -c 'CREATE DATABASE tymeslot OWNER tymeslot;'"
docker exec -i tymeslot su - postgres -c "psql -v ON_ERROR_STOP=1 --single-transaction tymeslot" < backup-20260101-020000.sql

3. Restart the application

docker restart tymeslot

Watch the logs: docker logs -f tymeslot. The start script runs any migrations the backup is missing and reports Database migrations completed successfully before the server starts.

Restore Overwrites All Existing Data

Restoring a backup replaces all data in the database; there is no partial restore. Bookings made since the backup was taken are lost, so check that you are restoring the right file to the right instance.

5 Restore the Data Volume

Extract the archive back into the Docker volume with a temporary Alpine container:

docker run --rm \
  -v tymeslot_data:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/data-20260101.tar.gz -C /data

No output means success. Verify that the files are back by listing the uploads directory: docker run --rm -v tymeslot_data:/data alpine ls /data/uploads

🧪 Test Your Backups

A backup you have never restored is a backup you cannot trust. Automated jobs fail silently: the file exists but holds an error message instead of SQL, or the dump stopped halfway. You do not want to find that out on the day you need it.

Once a month, restore the latest backup into a separate, throwaway container:

1. Start an isolated Postgres container

docker run -d \
  --name tymeslot-restore-test \
  -e POSTGRES_USER=tymeslot \
  -e POSTGRES_PASSWORD=testpassword \
  -e POSTGRES_DB=tymeslot \
  postgres:17-alpine

Wait a few seconds for Postgres to initialise inside the container.

2. Restore the backup into the test container

docker exec -i tymeslot-restore-test \
  psql -U tymeslot tymeslot \
  < backup-20260101-020000.sql

3. Spot-check critical data

Query a key table to confirm data is present:

docker exec -it tymeslot-restore-test \
  psql -U tymeslot tymeslot \
  -c "SELECT COUNT(*) AS user_count FROM users;"

The count should match the number of users on your instance. Zero means the restore failed or the backup holds no users.

4. Clean up the test container

docker rm -f tymeslot-restore-test

Schedule Regular Tests

Add a monthly calendar reminder to test a restore. Steps 1 to 4 above fit in a short shell script you can run as part of your maintenance routine.

❓ Frequently Asked Questions

My pg_dump command produces no output. Did it work?

Silence is success. pg_dump writes the dump to standard output, which the shell redirects into your file, so nothing reaches the terminal. Confirm the backup is complete and non-empty:

ls -lh backup-*.sql

A zero-byte or missing file means the redirect failed: check that you have write permission in the current directory.

I restored the database but the application won't start. What happened?

The most common cause is a schema mismatch between the backup and the application version. Check the logs for the specific error:

docker logs -f tymeslot

Look for migration errors or messages referencing missing columns or tables. This typically happens when you restore a backup taken from a newer version of Tymeslot onto an older image, whose code does not know the newer schema. Restore only backups taken from the same or an older version.

Can I restore a backup onto a newer version of Tymeslot?

Yes. Tymeslot runs all pending database migrations on startup, before it accepts traffic, so the newer image brings an older backup up to date on the next start. Each migration appears in the startup logs as a line beginning == Running. The direction to avoid is the reverse: a backup from a newer version restored onto an older image.

How much disk space will my backups use?

SQL dumps are plain text and compress well. Running gzip backup-*.sql after each dump saves space for long-term storage. The pruning cron entry matches .sql files, so change its pattern to tymeslot-*.sql.gz if you compress them.

The cron job runs but the backup file is empty or contains an error message instead of SQL.

This usually means the container name in the cron command does not match the running container. The docker exec command then fails and writes its error to the file through the 2>&1 redirect. List the running containers to find the right name:

docker ps

Update the cron command to use the exact name shown in the output. Also check that the cron user can run Docker commands and write to /backups.

Verify Your Setup

Confirm each of the following before considering your backup strategy complete:
  • Manual backup completes without errors. Run the pg_dump command manually and confirm the output file is non-empty.
  • Cron job is scheduled. Run crontab -l and confirm the backup entry is present.
  • A test restore succeeds. Follow the test procedure above and confirm the users table contains the expected row count.
  • Backups are stored off-server. At least the most recent backup is copied to a remote location outside the production server.
  • Old backups are pruned automatically. Confirm the cleanup cron entry is in place, or verify that your off-site storage has a retention policy configured.

🔗 Related Articles

Read Docker Self-Hosting

Docker Self-Hosting

Deploy Tymeslot with Docker Compose on any VPS or home server. Includes reverse proxy setup, file upload volumes, and WebSocket configuration for LiveView.

Read Upgrading Tymeslot

Upgrading Tymeslot

Upgrade Tymeslot with a single Docker command. Database migrations run automatically on startup. Includes rollback procedures and version compatibility notes.

Read Cloudron Deployment

Cloudron Deployment

Install Tymeslot on Cloudron in minutes. PostgreSQL, SSL certificates, email relay, and automatic updates are all provisioned and managed for you.