97 lines
5.5 KiB
Markdown
97 lines
5.5 KiB
Markdown
# 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 server
|
|
- `rezervator.sh` — creates, upgrades, backs up, restores and removes customers
|
|
- `config.template.toml` — template of a customer's configuration
|
|
- `.env.example` — settings of the stack
|
|
|
|
```text
|
|
┌─ 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, `bash` and `openssl` on 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
|
|
|
|
```sh
|
|
./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
|
|
|
|
```sh
|
|
./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:
|
|
|
|
```sh
|
|
./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
|
|
|
|
```sh
|
|
./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
|
|
|
|
```sh
|
|
./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_CONNECTIONS` database 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 `.env` contain 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.
|