Single or multi-tenant
One fepli installation can serve one organisation or many. Each organisation is a tenant, with its own domains, admins, settings and data. This page explains the two setups and how to create the first tenant and the first admin, which can only be done on the command line.
Tenants
A tenant is one organisation that uses fepli, such as a municipality with its holiday programme, a club or a library. It has:
- a slug, its machine name, e.g.
musterstadt, - a name, e.g. "Ferienpass Musterstadt", which also appears as the sender of its e-mails,
- its domains,
- its own admins, hosts, families, offers, settings and CMS pages.
All tenants of an installation share one database, and fepli keeps their data apart. A family registered with Musterstadt doesn't exist for Beispielhausen, and an admin of one tenant can't see the other. In the CMS, which all tenants share, a tenant's super admins edit their own website only: see Give the tenant its website.
There is no separate single-tenant version: every image is the same. Whether an installation is single- or multi-tenant only depends on how many tenants you create, and on whether you switch on the platform console.
| Single-tenant | Multi-tenant | |
|---|---|---|
| Tenants | one | as many as you need |
| Admin | at /admin on the organisation's domain | at /admin on each tenant's domain, plus the platform console |
| Tenants are created | once, on the command line | by global admins in the platform console, or on the command line |
FERIENPASS_PLATFORM_HOST | not set | the console's domain |
Start with a single tenant if you are unsure. You can add a platform console and more tenants at any time, without moving data.
Domains
Every tenant claims the domains it is reached on. A domain serves one of two things:
- A public host serves the tenant's website, and its admin under
/admin. This is the usual case:https://example.fepli.euandhttps://example.fepli.eu/admin. - An admin host serves only the admin, at the root of the domain, without
/admin. Once a tenant has an admin host, its public hosts stop serving the admin.
Two more kinds of domain serve no request at all, so they don't go into TRUSTED_HOSTS: the domain a tenant's e-mails are sent from (E-Mail-Versand), and the domain replies to its messages arrive at (E-Mail-Empfang, see E-mail replies).
A request for a domain no tenant claims is answered with 404, before anything else happens. fepli never falls back to a default tenant. So when you add a domain, add it in two places: to the tenant, and to TRUSTED_HOSTS.
Links in e-mails lead to the domains of the tenant that the account or record they are about belongs to. That holds for sign-in links, invitations, the link to set a new password that an admin sends, the link that confirms a new sign-in address, and every link in an e-mail the worker writes, wherever the e-mail was sent from: the platform console, an API domain or another tenant's domain included. A link into the admin goes to the tenant's admin host where it has one, otherwise to /admin on its public host. A link to the website, such as a family's sign-in link, goes to the public host. APP_BASE_URL is only the fallback, for a link without a tenant or a tenant without a domain for it.
In notification texts, {{ baseUrl }} is the address of the host the e-mail was sent from when that is one of the tenant's public or admin hosts. Sent from any other host, it is the tenant's public host, or its admin host if it has no public host.
Giving a tenant its first admin host moves its admin: from then on, /admin on its public hosts answers 404. Invitations and sign-in links sent before point there and stop working. Send open invitations and sign-in links again once the admin host is in place.
The platform console
On a multi-tenant installation, the platform console is the place to manage the installation itself. It has a domain of its own, e.g. platform.example.fepli.eu, set with FERIENPASS_PLATFORM_HOST. It belongs to no tenant and serves only the admin, at the root of the domain.
Only global admins can sign in there. They see:
- Installation → Mandanten: the list of tenants, with Mandant anlegen to create one, and the switch to take a tenant offline.
- Wechseln: stepping into a tenant, to work in its admin.
- CMS in the top bar: the CMS of the tenant they switched into, on its public host, without signing in again. There they administer the whole CMS. The tenant's log under Betroffenenrechte records CMS dieses Mandanten geöffnet, and Admin-Panel in the CMS leads back to the console.
- Installation → Systemwerte: the values all tenants share, such as the installation's sender address (Absenderadresse der Installation) and the maintenance mode, which puts every tenant's website behind a maintenance page. The admin stays reachable, and so do the files it offers for download.
On a new installation without tenants, the console shows nothing but the tenant list, until the first tenant exists.
Run commands in the container
The first admin is created with a console command inside the web container. With Docker Compose, prefix every command on this page with docker compose exec web:
Run a console command
docker compose exec web php bin/console ferienpass:tenant:list
Other platforms have their own way to open a shell in a running container (kubectl exec, the terminal of Dokploy or Coolify, …). Run the commands from /app, which is where the container starts.
Set up a single tenant
Create the tenant with its public hosts, then its first admin:
Create the tenant
php bin/console ferienpass:tenant:create musterstadt "Ferienpass Musterstadt" \
--host=example.fepli.eu \
--host=www.example.fepli.eu
Create the first admin
php bin/console ferienpass:user:create anna.schmidt@example.fepli.eu \
--tenant=musterstadt --firstname=Anna --lastname=Schmidt
The second command asks for the password twice. The admin is a super admin of Musterstadt: they may do everything in its admin, including payments, participants and the CMS, and invite everyone else from there. They sign in at https://example.fepli.eu/admin.
Then give the tenant its website.
Set up several tenants
-
Set
FERIENPASS_PLATFORM_HOSTto the console's domain, add that domain toTRUSTED_HOSTS, and restart the containers. -
Create a global admin. It belongs to no tenant, so it needs no
--tenant:Create a global admin
php bin/console ferienpass:user:create you@example.org --global-admin -
Sign in at
https://platform.example.fepli.euand create the tenants under Installation → Mandanten → Mandant anlegen, with their domains. Or create them on the command line withferienpass:tenant:create, as for a single tenant. -
Give each tenant its first admin: switch into the tenant and create its admins there, or use
ferienpass:user:createwith--tenant.
Global admins have the rights of a super admin in every tenant they switch into. The personal data of the tenant's families stays masked for them by default. Keep their number small.
The commands
- Name
ferienpass:tenant:create- Type
- slug name
- Description
Creates a tenant. The slug is kebab case (
musterstadt,bad-musterbach) and can't be changed later.--hostadds a public host,--admin-hostan admin host. Both can be repeated.
- Name
ferienpass:tenant:list- Description
Lists the tenants: slug, name, public and admin hosts, and whether they are online.
- Name
ferienpass:user:create- Type
- Description
Creates a super admin.
--tenant=<slug>names the tenant it belongs to,--global-adminmakes it an admin of the installation instead.--firstnameand--lastnameare optional.
Passwords:
- Run interactively,
ferienpass:user:createasks for the password. - Run without a terminal, e.g. from a script or with
docker compose exec -T, it generates a password and prints it once. Change it after signing in. --password=…works too, but leaves the password in your shell history and the process list. Prefer the prompt.
An e-mail address is unique per tenant, not per installation, so the same person can be an admin of two tenants with two accounts.
Give the tenant its website
The public website of a tenant is a page tree in the CMS, which the admin opens under CMS in its top bar. Each tenant's tree starts at exactly one root page, and the root page says which tenant it belongs to. Until the tenant has a published root page, its domain has no website, though its admin works. A tenant with only an admin host has no website and needs no root page.
The CMS has no Template Studio: fepli's templates come with the image.
One tenant
On an installation with one tenant, its super admins administer the whole CMS. They create the root page themselves:
- Sign in to the tenant's admin and open CMS in the top bar.
- Go to Seiten and create a page of the type Startpunkt einer Webseite.
- Set its Domainname to the tenant's public host, e.g.
example.fepli.eu, its Sprache tode, and choose the tenant under Mandant. - Publish it, and build the pages below it.
Several tenants
Once an installation has a second tenant, disabled tenants included, the CMS keeps the tenants apart. A tenant's super admins then edit:
- the pages and articles of their own website, the root page included, but not the tenant it belongs to,
- the files in the tenant's own folder,
files/<slug>, e.g.files/musterstadt, - the tenant's forms and FAQ categories, with their fields and questions. They can create and delete both, but can't let a form store what it receives in a database table.
A form or FAQ category belongs to one tenant, or to the installation. A new one belongs to the tenant it is created for: the tenant of the super admin who creates it, or, when a global admin creates it, the tenant whose public host the CMS runs on. Global admins hand it to another tenant, or to the installation, under Mandant, a field only they see.
Themes, layouts and front end modules belong to the whole installation, and only global admins change them. A tenant's super admins can't use the content elements Ungefiltertes HTML, Inhaltselement, Artikel and Artikelteaser; elements of these types already on their pages stay, but they can't change them.
A tenant's super admins can't add root pages either. So the first time one of them opens CMS from the tenant's admin, fepli prepares the website:
- An existing root page that names no tenant and carries one of the tenant's public hosts becomes the tenant's.
- Otherwise fepli creates a root page for the tenant: named after it, on its first public host, with the Sprache
deand the first layout of the installation, and not published. - fepli creates the folder
files/<slug>.
The super admin then publishes the root page and builds the pages below it.
Each time one of them opens CMS from the tenant's admin, forms and FAQ categories of the installation become the tenant's where they clearly belong to its website: a FAQ category whose reader page is part of it, and a form that only articles of its website show. Anything less clear stays with the installation, such as a form that another website or a front end module shows: a global admin assigns it, or copies a shared form for each tenant. Forms and FAQ categories from versions before they had a tenant belong to the installation at first, and find their tenant the same way.
A tenant's super admins get these rights on their next request in the CMS after the second tenant was created. Files uploaded before stay in use on their pages, but the file manager shows them only what lies in files/<slug>.