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.
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.
Aktualisiert am 11. März 2026
Requirements
pg_dump
available inside the Tymeslot container (no local install needed)
sudo
access on the host
What you will back up
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.
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.
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
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.
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
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
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.
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.
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
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:
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.
docker exec -i tymeslot-restore-test \
psql -U tymeslot tymeslot \
< backup-20260101-020000.sql
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.
docker rm -f tymeslot-restore-test
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.
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.
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.
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.
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.
pg_dump
command manually and confirm the output file is non-empty.
crontab -l
and confirm the backup entry is present.
users
table contains the expected row count.
Deploy Tymeslot with Docker Compose on any VPS or home server. Includes reverse proxy setup, file upload volumes, and WebSocket configuration for LiveView.
Upgrade Tymeslot with a single Docker command. Database migrations run automatically on startup. Includes rollback procedures and version compatibility notes.
Install Tymeslot on Cloudron in minutes. PostgreSQL, SSL certificates, email relay, and automatic updates are all provisioned and managed for you.