fepli
Hostingenterprise

Local setup

Run a complete fepli instance on your own machine, with the same image as in production. Use it to try fepli, to train colleagues or to rehearse an update. Nothing leaves your machine: every e-mail ends up in a mail catcher.

What you need

  • Docker Desktop, or Docker Engine with the Compose plugin, with about 4 GB of memory for its containers.
  • The user name and token fepli gave you for the image registry.
  • Chrome, Edge or Firefox. They send every *.localhost name to your own machine, which this setup uses for the tenants' domains. In other browsers, add 127.0.0.1 musterstadt.localhost to your hosts file.

1. Log in to the registry

Log in once

docker login ghcr.io --username <your user name>

Paste the token when Docker asks for the password. Docker remembers the login.

2. Describe the instance

Create a folder, e.g. fepli, with two files. The first describes the containers:

compose.yaml

name: fepli

x-fepli: &fepli
  image: ghcr.io/ferienpass/app:general-latest
  platform: linux/amd64
  env_file: fepli.env
  restart: unless-stopped
  depends_on:
    mariadb:
      condition: service_healthy
    redis:
      condition: service_started
  volumes:
    - files:/app/files
    - storage:/app/storage
    - images:/app/contao-assets/images
    - share:/app/public/share
    - indexes:/app/var/indexes
    - deferred-images:/app/var/deferred-images

services:
  web:
    <<: *fepli
    ports:
      - '8080:80'
    # The port opens once the database is migrated; the worker and the cron wait for it.
    healthcheck:
      test: ['CMD', 'php', '-r', 'exit(@fsockopen("127.0.0.1", (int) (getenv("PORT") ?: 80)) ? 0 : 1);']
      interval: 10s
      start_period: 10m

  worker:
    <<: *fepli
    environment:
      IS_WORKER: '1'
    depends_on:
      web:
        condition: service_healthy

  cron:
    <<: *fepli
    command: ['while true; do php bin/console contao:cron; sleep 60; done']
    depends_on:
      web:
        condition: service_healthy

  mariadb:
    image: mariadb:12.3
    restart: unless-stopped
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    environment:
      MARIADB_ROOT_PASSWORD: root
      MARIADB_DATABASE: fepli
      MARIADB_USER: fepli
      MARIADB_PASSWORD: fepli
    healthcheck:
      test: ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized']
      interval: 10s
      retries: 10
    volumes:
      - db:/var/lib/mysql

  redis:
    image: redis:8
    restart: unless-stopped
    command: ['redis-server', '--appendonly', 'yes', '--maxmemory-policy', 'noeviction']
    volumes:
      - redis:/data

  mailpit:
    image: axllent/mailpit
    ports:
      - '8025:8025'

volumes:
  db:
  redis:
  files:
  storage:
  images:
  share:
  indexes:
  deferred-images:

This setup runs without Varnish, which is simplest on your own machine. Add it when you want to try it.

The web, worker and cron containers are the same image with the same settings. Only their job differs: the worker gets IS_WORKER=1, and the cron runs the scheduled jobs once a minute. The volumes keep the uploaded files and the search index when you recreate the containers. The web and the worker share them, because the worker writes the exports the web container hands out.

The second file holds the configuration:

fepli.env

APP_SECRET=local-only-change-me-in-production
INTEGRATIONS_ENCRYPTION_KEY=local-only-change-me-in-production

DATABASE_URL=mysql://fepli:fepli@mariadb:3306/fepli
DATABASE_SERVER_VERSION=mariadb-12.3.0

REDIS_URL=redis://redis:6379/1
LOCK_DSN=redis://redis:6379/2
SESSION_DSN=redis://redis:6379/3
MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages

# Empty: no Varnish
VARNISH_HOST=

TRUSTED_HOSTS='^(localhost|[a-z0-9-]+\.localhost)$'
APP_BASE_URL=http://localhost:8080
FERIENPASS_PLATFORM_HOST=localhost

MAILER_DSN=smtp://mailpit:1025
ADMIN_EMAIL=ferienpass@musterstadt.de
SENTRY_DSN=

This makes localhost the platform console and lets every *.localhost name be a tenant, so you can try both single- and multi-tenant setups.

3. Start it

docker compose up -d

The first start downloads the images, which takes a few minutes. Then the web container creates fepli's tables before it starts to answer, and the worker and the cron start once it does. It does the same after every update: it changes only what the new version needs. docker compose ps shows whether everything runs; docker compose logs -f web shows what the web container does.

4. Create a tenant and its admin

Create the tenant Musterstadt, reachable at musterstadt.localhost, and its first admin:

Create the tenant

docker compose exec web php bin/console ferienpass:tenant:create musterstadt "Ferienpass Musterstadt" \
  --host=musterstadt.localhost

Create the admin

docker compose exec web php bin/console ferienpass:user:create anna.schmidt@musterstadt.de \
  --tenant=musterstadt --firstname=Anna --lastname=Schmidt

Choose a password when the command asks for one. Then:

OpenTo
http://musterstadt.localhost:8080/adminsign in as Anna and use the admin of Musterstadt
http://localhost:8025read every e-mail fepli sent

Musterstadt has no website yet: give it a root page in the CMS.

5. Try the platform console

For the console, create a global admin and sign in at http://localhost:8080:

Create a global admin

docker compose exec web php bin/console ferienpass:user:create you@example.org --global-admin

There you see Musterstadt in the tenant list, can step into it and can create more tenants, e.g. one at beispielhausen.localhost.

Add Varnish

To see fepli as it runs with a cache, put Varnish in front of the web container. Add the service to compose.yaml, and move the port from web to it:

compose.yaml

services:
  web:
    <<: *fepli
    # as above, but without ports: Varnish is the way in now

  varnish:
    image: ghcr.io/ferienpass/varnish:latest
    restart: unless-stopped
    environment:
      VARNISH_STORAGE: malloc,256m
    ports:
      - '8080:80'
    depends_on:
      - web

In fepli.env, tell fepli where Varnish is:

fepli.env

VARNISH_HOST=varnish:80
VARNISH_BASE_URL=http://varnish

Then docker compose up -d. The addresses stay the same. Remove the service and empty VARNISH_HOST again to go back.

Stop, update and start over

CommandWhat it does
docker compose stopstops everything and keeps the data
docker compose pull && docker compose up -dupdates to the latest image. The web container migrates the database when it starts.
docker compose down --volumesdeletes the containers and all data, to start over

When something doesn't work

  • A 400 for a domain: it doesn't match TRUSTED_HOSTS.
  • A 404 for a domain: no tenant claims it. Check the domains with ferienpass:tenant:list. With Varnish, if you opened the domain before a tenant claimed it, Varnish may still hold the 404: clear it with docker compose exec web php bin/console app:flush-varnish --all-tenants.
  • An error page right after the start: the web container is still creating the tables. Wait until docker compose logs web shows Starting server. If it stops instead, the log above says why.
  • No e-mail arrives in Mailpit: the worker sends them. Check that it runs with docker compose logs worker.
  • It is very slow on a Mac with Apple Silicon: the image is built for x86 and runs through emulation. Switch on "Use Rosetta for x86/amd64 emulation" in Docker Desktop's settings.

Was this page helpful?