Deployment setup for one container per customer #12

Closed
opened 2026-10-12 14:29:33 +02:00 by pepa · 1 comment
Owner

Goal: offer the app to tens, later hundreds, of customers. Each customer gets their own container from the same image; the app itself stays single-tenant.

Target setup

  • One image, one container per customer, differing only in configuration and data.
  • One shared PostgreSQL server with one database per customer.
  • A reverse proxy with automatic TLS (Traefik or Caddy) routing customer subdomains or custom domains to the containers.
  • Per customer: config file or environment, data volume (see the uploads issue), database, unique session key.

Tasks

  • Reference docker-compose (or equivalent) with the proxy, PostgreSQL and one example customer.
  • Provisioning script for a new customer: create database and database user, generate a unique session key and the initial admin password, create config, volume and container, register the domain in the proxy.
  • Upgrade procedure: roll a new image out customer by customer. Migrations run at start, so it must be possible to stop the rollout when one fails.
  • Backup and restore per customer (database and data volume).
  • Health check endpoint for the proxy and for monitoring.
  • Measure memory use of one idle instance to size the hosts. Not measured yet.
  • Verify the Docker build with Leptos 0.8; it has not been built since the upgrade.

Depends on

  • Config file outside the publicly served directory.
  • Persistent data directory for uploads.
  • No default admin password.
  • Configurable database pool size.

Later

Multi-tenancy (one instance serving all customers by domain) would remove the per-container overhead, but needs a tenant identifier in every table and query, in uploads and in e-mails. Revisit when operating hundreds of containers costs more than that rewrite.

Goal: offer the app to tens, later hundreds, of customers. Each customer gets their own container from the same image; the app itself stays single-tenant. ## Target setup - One image, one container per customer, differing only in configuration and data. - One shared PostgreSQL server with one database per customer. - A reverse proxy with automatic TLS (Traefik or Caddy) routing customer subdomains or custom domains to the containers. - Per customer: config file or environment, data volume (see the uploads issue), database, unique session key. ## Tasks - Reference `docker-compose` (or equivalent) with the proxy, PostgreSQL and one example customer. - Provisioning script for a new customer: create database and database user, generate a unique session key and the initial admin password, create config, volume and container, register the domain in the proxy. - Upgrade procedure: roll a new image out customer by customer. Migrations run at start, so it must be possible to stop the rollout when one fails. - Backup and restore per customer (database and data volume). - Health check endpoint for the proxy and for monitoring. - Measure memory use of one idle instance to size the hosts. Not measured yet. - Verify the Docker build with Leptos 0.8; it has not been built since the upgrade. ## Depends on - Config file outside the publicly served directory. - Persistent data directory for uploads. - No default admin password. - Configurable database pool size. ## Later Multi-tenancy (one instance serving all customers by domain) would remove the per-container overhead, but needs a tenant identifier in every table and query, in uploads and in e-mails. Revisit when operating hundreds of containers costs more than that rewrite.
Author
Owner

Two tasks from the list are done, and the four issues this one depends on (#8, #9, #10, #11) are closed.

Docker build verified

Built from Dockerfile after the Leptos 0.8 upgrade, on Docker Desktop 29.8.2 (linux/arm64): build passes in about 5 minutes, image size 243 MB. The page served from the container renders and hydrates in a browser.

Needed two changes first: a .dockerignore (the build context included the 34 GB target directory and the local config.toml, Maildir and data), and an architecture-aware download of cargo-binstall.

Not tried: a build for x86_64.

Checked against a container with an empty PostgreSQL 17 database:

Check Result
GET /data/config.toml (#8) 404
Config mounted in the old location or in the data directory container refuses to start
Empty database, no admin password (#10) refuses to start, no account created
admin as the initial password rejected as too short
Start with REZERVATOR_ADMIN_PASSWORD admin created; login with admin/admin fails, with the given password works
Banner uploaded as ../../test banner.PNG stored as test_banner.png in the data volume, nothing outside it
Container replaced, same /app/data volume (#9) banner and its style still served, login still works without the password variable
max_connections = 3 (#11) the instance held 3 connections

Memory use of one instance

  • idle: 7.4 MiB
  • after a few page loads, a login and an upload: 12.3 MiB
  • PostgreSQL 17 with this one small database, idle: 119 MiB

One instance, nearly empty database, no load — treat it as a lower bound. Even with a generous margin, memory per instance is not what limits hundreds of instances on one host; PostgreSQL and its connection limit are.

Still open

  • Reference docker-compose with the proxy, PostgreSQL and one example customer
  • Provisioning script for a new customer
  • Upgrade procedure
  • Backup and restore per customer
  • Health check endpoint
Two tasks from the list are done, and the four issues this one depends on (#8, #9, #10, #11) are closed. ## Docker build verified Built from `Dockerfile` after the Leptos 0.8 upgrade, on Docker Desktop 29.8.2 (linux/arm64): build passes in about 5 minutes, image size 243 MB. The page served from the container renders and hydrates in a browser. Needed two changes first: a `.dockerignore` (the build context included the 34 GB `target` directory and the local `config.toml`, `Maildir` and data), and an architecture-aware download of `cargo-binstall`. Not tried: a build for x86_64. Checked against a container with an empty PostgreSQL 17 database: | Check | Result | |---|---| | `GET /data/config.toml` (#8) | 404 | | Config mounted in the old location or in the data directory | container refuses to start | | Empty database, no admin password (#10) | refuses to start, no account created | | `admin` as the initial password | rejected as too short | | Start with `REZERVATOR_ADMIN_PASSWORD` | `admin` created; login with `admin`/`admin` fails, with the given password works | | Banner uploaded as `../../test banner.PNG` | stored as `test_banner.png` in the data volume, nothing outside it | | Container replaced, same `/app/data` volume (#9) | banner and its style still served, login still works without the password variable | | `max_connections = 3` (#11) | the instance held 3 connections | ## Memory use of one instance - idle: 7.4 MiB - after a few page loads, a login and an upload: 12.3 MiB - PostgreSQL 17 with this one small database, idle: 119 MiB One instance, nearly empty database, no load — treat it as a lower bound. Even with a generous margin, memory per instance is not what limits hundreds of instances on one host; PostgreSQL and its connection limit are. ## Still open - Reference `docker-compose` with the proxy, PostgreSQL and one example customer - Provisioning script for a new customer - Upgrade procedure - Backup and restore per customer - Health check endpoint
pepa closed this issue 2026-10-12 16:34:41 +02:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ITsolved/rezervovator#12