Database, migration and hardware
Bekci's default PostgreSQL + TimescaleDB install, upgrading from SQLite and plain PostgreSQL, hardware sizing, backups and rollback.
On this page
Bekci's default database is PostgreSQL 18 + TimescaleDB. Measurements are compressed, charts read precomputed rollups and old data is removed automatically. This page covers new installs, upgrading older installs and choosing hardware.
Which setup?#
| Setup | Status | Notes |
|---|---|---|
| PostgreSQL + TimescaleDB | Default, recommended | Installed with Docker Compose. The image version is pinned: timescale/timescaledb:2.31.0-pg18. |
| Plain PostgreSQL (RDS, Cloud SQL, etc.) | Works, not recommended | Without the extension Bekci still runs and shows a warning. It slows down at large scale: no compression, no rollups. |
| SQLite | Deprecated | Works in this version with a startup warning. Removed in the next major version. |
Embedded PostgreSQL (:postgres image) | Deprecated | Works in this version with a startup warning. Removed in the next major version. |
Settings → General shows which mode you are in: with SQLite or plain PostgreSQL a warning strip appears at the top of the page.
New install#
Follow the Docker Compose or HTTPS with Caddy guide; both use this setup. Nothing else is needed after creating the admin account: Bekci enables the extension and creates the tables.
POSTGRES_PASSWORD must contain letters and digits only; it is written into the connection address, so characters such as @, / and : break it. To generate one:
openssl rand -hex 24Upgrading from SQLite#
Migration is done with the uptime migrate command. It does not change your SQLite file: it takes a copy, upgrades the copy to the new version's schema and transfers it to PostgreSQL from there. Bekci must be stopped during the migration; no checks run in the meantime.
Take a backup. A full backup is the safest.
Add the
dbservice to yourdocker-compose.yml, setDATABASE_URLon thebekciservice and definePOSTGRES_PASSWORDin.env. Example: Docker Compose guide. Leave the/datavolume of thebekciservice as it is; the SQLite file lives there.Start the database and stop Bekci:
terminaldocker compose pull docker compose stop bekci docker compose up -d dbTry it first. This command writes nothing; it shows how much data will move and reports anything in the way:
terminaldocker compose run --rm bekci migrate --dry-runMigrate:
terminaldocker compose run --rm bekci migrateStart:
terminaldocker compose up -d
How long does it take? In our measurement 3.1 million rows moved at about 31,000 rows per second; with the conversion to compressed tables the total was 5 minutes 24 seconds (on a shared server). The conversion step shows no progress and can stay silent for minutes on large data; don't interrupt it.
If it's interrupted: run the same command again and it continues where it stopped. With a half-finished migration the panel refuses to start with SQLite verisi taşınmadı (“SQLite data not migrated”). This is a safeguard against running on partial data.
If the target isn't empty: the command won't start, because it never overwrites existing data. This usually happens when an empty install was opened with DATABASE_URL earlier. If you don't want the old data and prefer to start fresh, set SQLITE_IGNORE=1 in .env; the panel then starts and ignores the SQLite file.
Command options#
| Option | Description |
|---|---|
--from | SQLite file. Default: /data/uptime.db |
--to | Target address. Default: DATABASE_URL |
--batch | Rows copied in one transaction |
--work-dir | Folder for the temporary copy. If /data lacks space, point to another volume; it needs as much free space as the SQLite file. |
--restart | Discards a half-finished migration and starts over |
--dry-run | Checks without writing |
Upgrading from plain PostgreSQL#
If Bekci is connected to a PostgreSQL server through DATABASE_URL, you don't need to move data: installing the extension on the server is enough. Bekci converts the tables itself at startup.
The postgres:18-alpine container in Compose: change the image of the db service and add command so the existing data folder loads the extension:
db:
image: timescale/timescaledb:2.31.0-pg18
command: postgres -c shared_preload_libraries=timescaledb -c max_worker_processes=24docker compose pull
docker compose up -dThe major version must match (18). Don't use this route from an older major version or from the Debian-based postgres:18 image; the collation library difference can corrupt indexes. Use the dump route below instead.
Dump and restore: dump the old server, start the new db service empty, restore, start Bekci:
pg_dump --format=custom --no-owner --file=bekci.dump "postgres://user:password@old-server:5432/bekci"
docker compose up -d db
docker compose exec -T db pg_restore -U bekci -d bekci --no-owner < bekci.dump
docker compose up -dManaged PostgreSQL (RDS, Cloud SQL, etc.): if the service doesn't offer TimescaleDB, Bekci keeps running in plain mode. It works but isn't recommended; it slows down at large scale.
Existing tables convert to hypertables on the panel's first start. The panel is down meanwhile; the time depends on data size (measured: 1.2 million rows on a shared server, 26 seconds to 2.5 minutes).
Upgrading from embedded PostgreSQL#
The :postgres image is deprecated. Move the data to a separate db service with a dump:
docker exec bekci pg_dump -h /run/postgresql -U postgres --format=custom --no-owner --file=/data/backups/tasima.dump uptime
docker cp bekci:/data/backups/tasima.dump ./tasima.dump
docker stop bekciChange your Compose file to the default setup (a version number instead of the postgres tag), then:
docker compose up -d db
docker compose exec -T db pg_restore -U bekci -d bekci --no-owner < tasima.dump
docker compose up -dThe old /data/postgres folder isn't deleted; keep it until you are sure the new setup works.
Hardware sizing#
Assumption: 5 locations, 30-second check interval. The 100 and 1,000 monitor rows are based on measurements; the 10,000 row is a calculation, not tested at full load.
| Monitors | RAM | shared_buffers | CPU | Disk (raw data 90 days) | Disk (raw data 14 days) |
|---|---|---|---|---|---|
| 100 | 4 GB (minimum 2 GB) | 1 GB | 2 cores | 15 GB | 5 GB |
| 1,000 | 8 GB, comfortably 16 GB | 2-4 GB | 4 cores | 40 GB | 20 GB |
| 10,000 | 32 GB minimum, 64 GB recommended, NVMe | 8-16 GB | 8+ cores | 200 GB | 80 GB |
Set shared_buffers on the db service:
db:
command: postgres -c shared_preload_libraries=timescaledb -c max_worker_processes=24 -c shared_buffers=2GBOn a memory-limited server set TS_TUNE_MEMORY: 400MB on the db service; on first start the image picks its settings from the machine's total memory. The timescale image itself takes about 1.6 GB of disk.
Backup and restore#
Bekci writes an uptime-DATE.dump file under /data/backups/ every night. To take one by hand:
docker compose exec -T db pg_dump -U bekci --format=custom --no-owner bekci > bekci.dumpRestoring a TimescaleDB dump needs two extra steps. The target database must be empty:
docker compose stop bekci
docker compose exec db psql -U bekci -d postgres -c 'DROP DATABASE bekci' -c 'CREATE DATABASE bekci'
docker compose exec db psql -U bekci -d bekci -c 'SELECT timescaledb_pre_restore()'
docker compose exec -T db pg_restore -U bekci -d bekci --no-owner < bekci.dump
docker compose exec db psql -U bekci -d bekci -c 'SELECT timescaledb_post_restore()'
docker compose start bekciIn our measurement dumping a database of 3 million rows took 7 seconds and used 532 MB.
Rollback#
Back to SQLite: uptime migrate doesn't change the SQLite file, so the old install stays intact.
docker compose stop bekci- Delete the
DATABASE_URLline indocker-compose.yml. - Set
UPTIME_TAG=1.6.0in.env. docker compose up -d
Measurements and incidents written to PostgreSQL after the migration aren't in the SQLite file and are lost on rollback. Don't delete the SQLite file until you are sure everything works.
To migrate again after running on SQLite for a while, empty the target database first (docker compose down and delete its volume, or DROP DATABASE). Otherwise uptime migrate reports "already completed" and doesn't copy the new data.
Back to plain PostgreSQL: tables converted to TimescaleDB don't open on a plain PostgreSQL image. Restore the dump you took before upgrading onto the old server.
Back to the embedded image: use the old tag and the /data/postgres folder; the upgrade doesn't change that folder.
Something missing or wrong on this page? Report it on GitHub