Running Rezervovator for many customers
The application serves one company per instance. To host many customers, every customer gets their own container from the same image, with their own database, configuration and data volume. This directory contains what is needed to run that on one Docker host:
docker-compose.yml— the shared part: the reverse proxy (Traefik) and one PostgreSQL serverrezervator.sh— creates, upgrades, backs up, restores and removes customersconfig.template.toml— template of a customer's configuration.env.example— settings of the stack
┌─ rezervator-app-alpha ── database rz_alpha, volume rezervator-data-alpha
internet ── proxy ────┼─ rezervator-app-beta ── database rz_beta, volume rezervator-data-beta
(TLS, by └─ ...
domain) all databases live in the rezervator-postgres container
Requirements
- Docker with the Compose plugin,
bashandopensslon the host - The application image on the host (
docker build -t rezervator:latest .in the repository root, or pulled from a registry) - For HTTPS: a DNS record for every customer domain pointing to the host, and ports 80 and 443 reachable from the internet
First setup
./rezervator.sh init # creates .env with a generated PostgreSQL password and stops
$EDITOR .env # image, TLS, ACME_EMAIL, ports
$EDITOR config.template.toml # the [mailing] section: your SMTP server
./rezervator.sh init # starts the proxy and PostgreSQL
REZERVATOR_TLS=true gets a Let's Encrypt certificate for every customer domain and redirects HTTP to HTTPS. Use false only for local testing or behind another proxy that terminates TLS: the session cookie is marked Secure, so logging in over plain HTTP works only on localhost addresses.
Adding a customer
./rezervator.sh add alpha rezervace.alpha.example
This creates the database and its user, writes customers/alpha/config.toml with a unique session key and database password, creates the data volume, starts the container and waits until it is healthy. At the end it prints the generated password of the admin account — it is shown only once.
An optional third argument sets the sender address of e-mails (default noreply@<domain>).
./rezervator.sh list shows all customers and the state of their containers.
Upgrading
Build or pull the new image under the name set in REZERVATOR_IMAGE, then:
./rezervator.sh upgrade alpha # try one customer first
./rezervator.sh upgrade --all
Customers are upgraded one by one. For each one the script makes a backup, replaces the container (database migrations run when the new version starts) and waits for it to become healthy. Customers that already run the image are skipped, so an interrupted upgrade can simply be run again.
If a customer does not become healthy, the script puts back the database and data from the backup it just made, starts the previous image again and stops. The remaining customers are not touched. The customer is unavailable for the duration of the attempt.
Backup and restore
./rezervator.sh backup --all
./rezervator.sh restore alpha 20260101T020000Z
A backup is two files in backups/<name>/: <timestamp>-db.sql.gz (database dump) and <timestamp>-data.tar.gz (uploaded banner and generated styles). restore replaces both the database and the data volume, and keeps the image the customer was running.
The script does not schedule backups, does not delete old ones and does not copy them off the host. Run backup --all from cron, and ship backups/, customers/ and .env somewhere else — customers/ and .env hold the passwords and session keys and cannot be regenerated.
Removing a customer
./rezervator.sh remove alpha # stops the container, keeps everything else
./rezervator.sh remove alpha --purge # also deletes the database, data volume and config
After a plain remove, upgrade alpha starts the customer again. --purge asks for the customer name as confirmation and keeps existing backups.
Health and monitoring
Every instance answers GET /health with 200 when it can reach its database and with 503 otherwise. The image uses it as its Docker health check, and Traefik sends traffic only to healthy containers. Point an external monitor to https://<domain>/health.
Sizing
- One idle instance used about 7 MiB of memory in a test, about 12 MiB after some use. PostgreSQL itself needs far more than that.
- Every instance may open up to
CUSTOMER_MAX_CONNECTIONSdatabase connections (5 by default; it is written into the customer's config when the customer is added) and closes idle ones.PG_MAX_CONNECTIONS(300 by default) must stay above the number of customers times that limit. Beyond that, put PgBouncer in front of PostgreSQL.
Security notes
customers/and.envcontain secrets and are readable only by the user who ran the script. Keep them out of version control (they are in.gitignore).- The proxy has read access to the Docker socket to discover containers. That is equivalent to root on the host; do not expose the proxy's API or dashboard.
- Customers share one PostgreSQL server but each has its own database and role, without access to the others.
Running a second stack
Change REZERVATOR_PREFIX (and the ports) in .env of another copy of this directory. All container, network and volume names are derived from the prefix, and each proxy only sees the containers of its own stack.