diff --git a/README.md b/README.md
index 79144a89..91066d96 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,42 @@
 ## Quickstart Instructions to try it out
 
 * Make sure you have docker and docker-compose available.
-* Run 'make deploy' in the base directory.
+* Change to the base directory of this repository.
+* Run 'HOSTNAME=kolab.local ADMIN_PASSWORD="simple123" bin/configure.sh config.prod' to configure this deployment.
+* Run 'bin/deploy.sh' to start the deployment.
+* Run 'docker exec -w /src/kolabsrc/ kolab-webapp ./artisan user:password admin@kolab.local simple123' to set your admin password
 * Add an /etc/hosts entry  "127.0.0.1 kolab.local"
 * navigate to https://kolab.local
-* login as "john@kolab.org" with password "simple123"
+* login as "admin@kolab.local" with password "simple123" (or whatever you have set), and create your users.
 
-# Setup env.local
+# Customization
 
-To customize the installation, create a file src/env.local to override setting in src/.env.example.
+To customize the installation, copy config.prod and adjust to your liking. You can then install the configuration using 'bin/configure.sh $YOURCONFIG',
+and afterwards 'bin/deploy.sh' again.
 
-The setup script with merge these settings into src/.env, which is what is ultimately used by the installation.
-
-Take a look at ansible/env.local for an example of typical modifications required for an installation.
+Please note that bin/deploy.sh will remove any existing data.
 
 # Use the ansible setup
 
 The ansible/ directory contains setup scripts to setup a fresh Fedora system with a kolab deployment.
 Modify the Makefile with the required variables and then execute `make setup`.
 
-This will configure the remote system and execute bin/deploy.sh
+This will configure the remote system and execute the above steps.
 
 ### Update
 
+To update the containers without removing the data:
+
 * git pull
 * Run "bin/update.sh"
 
 ### Backup / Restore
 
 The "bin/backup.sh" script will stop all containers, snapshot the volumes to the backup/ directory, and restart the containers.
 
 "bin/restore.sh"  will stop all containers, restore the volumes from tarballs in the backup/ directory, and restart the containers.
 
 
 ### Requirements
 * docker
 * openssl
-
-## TODO
-* Only seed admin user, but not all the development stuff?
diff --git a/ansible/Makefile b/ansible/Makefile
index 55869f2d..1f5bdcae 100644
--- a/ansible/Makefile
+++ b/ansible/Makefile
@@ -1,10 +1,11 @@
 OX_API_KEY=
 FIREBASE_API_KEY=
 HOSTNAME=
 PUBLIC_IP=
+ADMIN_PASSWORD=
 GIT_BRANCH=beta-release
 
 setup:
 	touch ./hosts
 	echo "${HOSTNAME}" > ./hosts
-	ansible-playbook -v --inventory=./hosts --extra-vars="hostname=${HOSTNAME} openexchangerates_api_key=${OX_API_KEY} firebase_api_key=${FIREBASE_API_KEY} public_ip=${PUBLIC_IP} git_branch=${GIT_BRANCH}" setup.yml
+	ansible-playbook -v --inventory=./hosts --extra-vars="hostname=${HOSTNAME} openexchangerates_api_key=${OX_API_KEY} firebase_api_key=${FIREBASE_API_KEY} public_ip=${PUBLIC_IP} admin_password=${ADMIN_PASSWORD} git_branch=${GIT_BRANCH}" setup.yml
diff --git a/ansible/env.local b/ansible/env.local
deleted file mode 100644
index 54fe6ecd..00000000
--- a/ansible/env.local
+++ /dev/null
@@ -1,43 +0,0 @@
-APP_DOMAIN={{ host }}
-APP_WEBSITE_DOMAIN={{ host }}
-APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E=
-APP_PUBLIC_URL=https://{{ host }}
-COTURN_STATIC_SECRET=uzYguvIl9tpZFMuQOE78DpOi6Jc7VFSD0UAnvgMsg5n4e74MgIf6vQvbc6LWzZjz
-COTURN_PUBLIC_IP='{{ public_ip }}'
-MEET_TURN_SERVER='turn:{{ public_ip }}:3478'
-MEET_WEBRTC_LISTEN_IP='{{ public_ip }}'
-MEET_PUBLIC_DOMAIN={{ host }}
-MEET_SERVER_URLS=https://{{ host }}/meetmedia/api/
-APP_URL=https://{{ host }}
-ASSET_URL=https://{{ host }}
-
-DB_HOST=mariadb
-REDIS_HOST=redis
-IMAP_URI=ssl://kolab:11993
-LDAP_HOSTS=kolab
-
-MOLLIE_KEY=
-STRIPE_KEY=
-STRIPE_PUBLIC_KEY=
-STRIPE_WEBHOOK_SECRET=
-
-OCTANE_HTTP_HOST={{ host }}
-OPENEXCHANGERATES_API_KEY={{ openexchangerates_api_key }}
-FIREBASE_API_KEY={{ firebase_api_key }}
-
-#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
-PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee
-PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf
-
-#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
-PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb
-PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz
-
-APP_PASSPHRASE=simple123
-
-KOLAB_SSL_CERTIFICATE=/etc/letsencrypt/live/{{ host }}/cert.pem
-KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/letsencrypt/live/{{ host }}/fullchain.pem
-KOLAB_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/{{ host }}/privkey.pem
-
-PROXY_SSL_CERTIFICATE=/etc/letsencrypt/live/{{ host }}/fullchain.pem
-PROXY_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/{{ host }}/privkey.pem
diff --git a/ansible/setup.yml b/ansible/setup.yml
index 32cc8831..1dac567e 100755
--- a/ansible/setup.yml
+++ b/ansible/setup.yml
@@ -1,121 +1,122 @@
 #!/usr/bin/ansible-playbook
 - name: Setup kolab deployment on fedora server
   hosts: "{{ hostname }}"
   remote_user: root
   tasks:
     - import_tasks: grub.yml
 
     - name: Set hostname
       ansible.builtin.hostname:
         name: "{{ hostname }}"
 
     - import_tasks: packages.yml
 
     - name: Put SELinux in permissive mode for docker
       selinux:
         policy: targeted
         state: permissive
 
     - name: Setup user kolab
       ansible.builtin.user:
         name: kolab
         shell: /bin/bash
         groups: wheel, audio, docker
         append: yes
 
     - name: sudo without password
       ansible.builtin.lineinfile:
         path: /etc/sudoers
         state: present
         regexp: '^%wheel\s'
         line: '%wheel ALL=(ALL) NOPASSWD: ALL'
 
     - name: Start service docker, if not started
       ansible.builtin.service:
         name: docker
         state: started
 
     - import_tasks: certbot.yml
 
     - name: get kolab git repo
       become: true
       become_user: kolab
       git:
         repo: https://git.kolab.org/source/kolab.git
         dest: /home/kolab/kolab
         version: "{{ git_branch }}"
         force: yes
 
-    - name: "kolab env.local"
-      vars:
-        host: "{{ hostname }}"
-        openexchangerates_api_key: "{{ openexchangerates_api_key }}"
-        firebase_api_key: "{{ firebase_api_key }}"
-        public_ip: "{{ public_ip }}"
-      ansible.builtin.template:
-        src: env.local
-        dest: /home/kolab/kolab/src/env.local
-        owner: kolab
-        group: kolab
-        mode: '0766'
+    - name: Run bin/configure
+      become: true
+      become_user: kolab
+      ansible.builtin.command: bin/configure.sh config.production
+      args:
+        chdir: /home/kolab/kolab
+      environment:
+        HOSTNAME: "{{ hostname }}"
+        OPENEXCHANGERATES_API_KEY: "{{ openexchangerates_api_key }}"
+        FIREBASE_API_KEY: "{{ firebase_api_key }}"
+        PUBLIC_IP: "{{ public_ip }}"
+        ADMIN_PASSWORD: "{{ admin_password }}"
+
 
     - name: Permit receiving mail
       firewalld:
         port: 25/tcp
         permanent: yes
         state: enabled
         zone: FedoraServer
 
     - name: Permit http traffic
       firewalld:
         port: 80/tcp
         permanent: yes
         state: enabled
         zone: FedoraServer
 
     - name: Permit https traffic
       firewalld:
         port: 443/tcp
         permanent: yes
         state: enabled
         zone: FedoraServer
 
     - name: Permit TCP trafic for coturn
       firewalld:
         port: 3478/tcp
         permanent: yes
         state: enabled
         zone: FedoraServer
 
     - name: Permit TCP trafic for coturn
       firewalld:
         port: 5349/tcp
         permanent: yes
         state: enabled
         zone: FedoraServer
 
     - name: Permit UDP trafic for coturn
       firewalld:
         port: 3478/udp
         permanent: yes
         state: enabled
         zone: FedoraServer
 
     - name: Permit UDP trafic for coturn
       firewalld:
         port: 5349/udp
         permanent: yes
         state: enabled
         zone: FedoraServer
 
     - name: Always restart docker before deploy (because of potential network issues otherwise)
       ansible.builtin.service:
         name: docker
         state: restarted
 
     - name: Run bin/deploy
       become: true
       become_user: kolab
       ansible.builtin.command: bin/deploy.sh
       args:
         chdir: /home/kolab/kolab
diff --git a/bin/configure.sh b/bin/configure.sh
new file mode 100755
index 00000000..ac385384
--- /dev/null
+++ b/bin/configure.sh
@@ -0,0 +1,76 @@
+#!/bin/bash
+
+# Uninstall the old config
+if [ -d config ]; then
+    echo "Uninstalling the old config."
+    find -L config/ -type f | while read file; do
+        file=$(echo $file | sed -e 's|^config||g')
+        file="./$file"
+
+        rm -v $file
+    done
+fi
+
+if [ "$1" == "" ]; then
+    echo "Failed to find the configuration folder, please pass one as argument (e.g. config.demo)."
+    exit 1
+fi
+
+if [ ! -d $1 ]; then
+    echo "Failed to find the configuration folder, please pass one as argument (e.g. config.demo)."
+    exit 1
+fi
+
+echo "Installing $1."
+# Link new config
+rm config
+ln -s $1 config
+
+# Install new config
+find -L config/ -type f | while read file; do
+    dir=$(dirname $file | sed -e 's|^config||g')
+    dir="./$dir"
+
+    if [ ! -d $dir ]; then
+        mkdir -p $dir
+    fi
+
+    cp -v $file $dir/
+done
+
+
+# Generate random secrets
+if ! grep -q "COTURN_STATIC_SECRET" .env; then
+    COTURN_STATIC_SECRET=$(openssl rand -hex 32);
+    echo "COTURN_STATIC_SECRET=${COTURN_STATIC_SECRET}" >> src/.env
+fi
+
+if ! grep -q "MEET_WEBHOOK_TOKEN" .env; then
+    MEET_WEBHOOK_TOKEN=$(openssl rand -hex 32);
+    echo "MEET_WEBHOOK_TOKEN=${MEET_WEBHOOK_TOKEN}" >> src/.env
+fi
+
+if ! grep -q "MEET_SERVER_TOKEN" .env; then
+    MEET_SERVER_TOKEN=$(openssl rand -hex 32);
+    echo "MEET_SERVER_TOKEN=${MEET_SERVER_TOKEN}" >> src/.env
+fi
+
+# Customize configuration
+sed -i \
+    -e "s/{{ host }}/${HOSTNAME}/g" \
+    -e "s/{{ openexchangerates_api_key }}/${OPENEXCHANGERATES_API_KEY}/g" \
+    -e "s/{{ firebase_api_key }}/${FIREBASE_API_KEY}/g" \
+    -e "s/{{ public_ip }}/${PUBLIC_IP}/g" \
+    -e "s/{{ admin_password }}/${ADMIN_PASSWORD}/g" \
+    src/.env
+
+if [ -f /etc/letsencrypt/live/${HOSTNAME}/cert.pem ]; then
+    echo "Using the available letsencrypt certificate for ${HOSTNAME}"
+    cat >> .env << EOF
+KOLAB_SSL_CERTIFICATE=/etc/letsencrypt/live/${HOSTNAME}/cert.pem
+KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem
+KOLAB_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/${HOSTNAME}/privkey.pem
+PROXY_SSL_CERTIFICATE=/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem
+PROXY_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/${HOSTNAME}/privkey.pem
+EOF
+fi
diff --git a/bin/deploy.sh b/bin/deploy.sh
index a036a0b2..07f8c7e4 100755
--- a/bin/deploy.sh
+++ b/bin/deploy.sh
@@ -1,4 +1,2 @@
 #!/bin/bash
 bin/quickstart.sh --nodev
-
-docker exec -w /src/kolabsrc/ kolab-webapp ./artisan user:assign john@kolab.org beta
diff --git a/bin/quickstart.sh b/bin/quickstart.sh
index 224b4637..f48ff50c 100755
--- a/bin/quickstart.sh
+++ b/bin/quickstart.sh
@@ -1,157 +1,141 @@
 #!/bin/bash
 
 set -e
 
 function die() {
     echo "$1"
     exit 1
 }
 
 rpm -qv docker-compose >/dev/null 2>&1 || \
     test ! -z "$(which docker-compose 2>/dev/null)" || \
     die "Is docker-compose installed?"
 
 test ! -z "$(grep 'systemd.unified_cgroup_hierarchy=0' /proc/cmdline)" || \
     die "systemd containers only work with cgroupv1 (use 'grubby --update-kernel=ALL --args=\"systemd.unified_cgroup_hierarchy=0\"' and a reboot to fix)"
 
 base_dir=$(dirname $(dirname $0))
 
-# Always reset .env with .env.example
-cp src/.env.example src/.env
 
-if [ -f "src/env.local" ]; then
-    # Ensure there's a line ending
-    echo "" >> src/.env
-    cat src/env.local >> src/.env
-fi
 
 export DOCKER_BUILDKIT=0
 
-COMPOSE_ARGS=
-if [ "$1" != "--nodev" ]; then
-    COMPOSE_ARGS="-f docker-compose.yml -f docker-compose.local.yml"
-fi
-
 docker-compose down --remove-orphans
 docker volume rm kolab_mariadb || :
 docker volume rm kolab_imap || :
 docker volume rm kolab_ldap || :
 
-if [ "$1" != "--nodev" ]; then
-    src/artisan octane:stop >/dev/null 2>&1 || :
-    src/artisan horizon:terminate >/dev/null 2>&1 || :
-else
-    # If we switch from an existing development setup to a compose deployment,
-    # we don't have a nice way to terminate octane/horizon.
-    # We can't use the artisan command because it will just block if redis is,
-    # no longer available, so we just kill all artisan processes running.
-    pkill -9 -f artisan || :
-fi
+# We can't use the following artisan commands because it will just block if redis is unavailable:
+# src/artisan octane:stop >/dev/null 2>&1 || :
+# src/artisan horizon:terminate >/dev/null 2>&1 || :
+# we therefore just kill all artisan processes running.
+pkill -9 -f artisan || :
+pkill -9 -f swoole || :
 
 bin/regen-certs
 docker-compose build coturn kolab mariadb meet pdns proxy redis haproxy
-docker-compose ${COMPOSE_ARGS} up -d coturn kolab mariadb meet pdns redis
+docker-compose up -d coturn kolab mariadb meet pdns redis
 
 # Workaround until we have docker-compose --wait (https://github.com/docker/compose/pull/8777)
 function wait_for_container {
     container_id="$1"
     container_name="$(docker inspect "${container_id}" --format '{{ .Name }}')"
     echo "Waiting for container: ${container_name} [${container_id}]"
     waiting_done="false"
     while [[ "${waiting_done}" != "true" ]]; do
         container_state="$(docker inspect "${container_id}" --format '{{ .State.Status }}')"
         if [[ "${container_state}" == "running" ]]; then
             health_status="$(docker inspect "${container_id}" --format '{{ .State.Health.Status }}')"
             echo "${container_name}: container_state=${container_state}, health_status=${health_status}"
             if [[ ${health_status} == "healthy" ]]; then
                 waiting_done="true"
             fi
         else
             echo "${container_name}: container_state=${container_state}"
             waiting_done="true"
         fi
         sleep 1;
     done;
 }
 
 if [ "$1" == "--nodev" ]; then
     echo "starting everything in containers"
     docker-compose -f docker-compose.build.yml build swoole
     docker-compose build webapp
     docker-compose up -d webapp proxy haproxy
     wait_for_container 'kolab-webapp'
     exit 0
 fi
 echo "Starting the development environment"
 
 rpm -qv composer >/dev/null 2>&1 || \
     test ! -z "$(which composer 2>/dev/null)" || \
     die "Is composer installed?"
 
 rpm -qv npm >/dev/null 2>&1 || \
     test ! -z "$(which npm 2>/dev/null)" || \
     die "Is npm installed?"
 
 rpm -qv php >/dev/null 2>&1 || \
     test ! -z "$(which php 2>/dev/null)" || \
     die "Is php installed?"
 
 rpm -qv php-ldap >/dev/null 2>&1 || \
     test ! -z "$(php --ini | grep ldap)" || \
     die "Is php-ldap installed?"
 
 rpm -qv php-mysqlnd >/dev/null 2>&1 || \
     test ! -z "$(php --ini | grep mysql)" || \
     die "Is php-mysqlnd installed?"
 
 test ! -z "$(php --modules | grep swoole)" || \
     die "Is swoole installed?"
 
 # Ensure the containers we depend on are fully started
 wait_for_container 'kolab'
 wait_for_container 'kolab-redis'
 
 pushd ${base_dir}/src/
 
 rm -rf vendor/ composer.lock
 php -dmemory_limit=-1 $(which composer) install
 npm install
 find bootstrap/cache/ -type f ! -name ".gitignore" -delete
 ./artisan key:generate
 ./artisan clear-compiled
 ./artisan cache:clear
 ./artisan horizon:install
 
 if [ ! -f storage/oauth-public.key -o ! -f storage/oauth-private.key ]; then
     ./artisan passport:keys --force
 fi
 
 cat >> .env << EOF
 PASSPORT_PRIVATE_KEY="$(cat storage/oauth-private.key)"
 PASSPORT_PUBLIC_KEY="$(cat storage/oauth-public.key)"
 EOF
 
 
 if rpm -qv chromium 2>/dev/null; then
     chver=$(rpmquery --queryformat="%{VERSION}" chromium | awk -F'.' '{print $1}')
     ./artisan dusk:chrome-driver ${chver}
 fi
 
 if [ ! -f 'resources/countries.php' ]; then
     ./artisan data:countries
 fi
 
 npm run dev
 popd
 
 pushd ${base_dir}/src/
 rm -rf database/database.sqlite
 ./artisan db:ping --wait
 php -dmemory_limit=512M ./artisan migrate:refresh --seed
 ./artisan data:import || :
 nohup ./artisan octane:start --host=$(grep OCTANE_HTTP_HOST .env | tail -n1 | sed "s/OCTANE_HTTP_HOST=//") > octane.out &
 nohup ./artisan horizon > horizon.out &
 
 popd
 
-docker-compose ${COMPOSE_ARGS} up --no-deps -d proxy haproxy
+docker-compose up --no-deps -d proxy haproxy
diff --git a/ci/Makefile b/ci/Makefile
index 5a19745c..5391be7e 100644
--- a/ci/Makefile
+++ b/ci/Makefile
@@ -1,38 +1,30 @@
-HOSTNAME=ci.local
-PUBLIC_IP=127.0.0.1
-OPENEXCHANGERATES_API_KEY=dummy
-FIREBASE_API_KEY=dummy
 PWD=$(shell pwd)
 
 
 configure:
 	cd .. ; \
-	cp ci/env.local src/env.local ; \
-	sed -i 's/{{ host }}/${HOSTNAME}/g' src/env.local ; \
-	sed -i 's/{{ public_ip }}/${PUBLIC_IP}/g' src/env.local ; \
-	sed -i 's/{{ openexchangerates_api_key }}/${OPENEXCHANGERATES_API_KEY}/g' src/env.local ; \
-	sed -i 's/{{ firebase_api_key }}/${FIREBASE_API_KEY}/g' src/env.local ;
+	bin/configure.sh config.demo ;
 
 setup:
 	cd .. && bin/quickstart.sh --nodev
 
 build:
 	cd .. && DOCKER_BUILDKIT=0 docker compose -f docker-compose.yml -f docker-compose.build.yml build swoole && DOCKER_BUILDKIT=0 docker compose -f docker-compose.yml -f docker-compose.build.yml build tests && cd ci
 
 lint:
 	docker kill kolab-tests || : ; \
 	kolab rm kolab-tests || : ; \
 	docker run -v ${PWD}/../:/src/kolab.orig -t kolab-tests /lint.sh
 
 test:
 	docker kill kolab-tests || : ; \
 	docker rm kolab-tests || : ; \
 	docker run --network=kolab_kolab -v ${PWD}/../src:/src/kolabsrc.orig -t kolab-tests /init.sh testsuite
 
 shell:
 	docker kill kolab-tests || : ; \
 	docker rm kolab-tests || : ; \
 	docker run --network=kolab_kolab -v ${PWD}/../src:/src/kolabsrc.orig -ti kolab-tests /init.sh shell
 
 all: configure build setup lint test
 
diff --git a/ci/env.local b/ci/env.local
deleted file mode 100644
index 3991e0db..00000000
--- a/ci/env.local
+++ /dev/null
@@ -1,49 +0,0 @@
-MFA_DSN=mysql://root:Welcome2KolabSystems@mariadb/roundcube
-APP_DOMAIN={{ host }}
-APP_WEBSITE_DOMAIN={{ host }}
-APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E=
-APP_PUBLIC_URL=https://{{ host }}
-COTURN_STATIC_SECRET=uzYguvIl9tpZFMuQOE78DpOi6Jc7VFSD0UAnvgMsg5n4e74MgIf6vQvbc6LWzZjz
-COTURN_PUBLIC_IP='{{ public_ip }}'
-MEET_TURN_SERVER='turn:{{ public_ip }}:3478'
-MEET_WEBRTC_LISTEN_IP='{{ public_ip }}'
-MEET_PUBLIC_DOMAIN={{ host }}
-MEET_SERVER_URLS=https://{{ host }}/meetmedia/api/
-MEET_LISTENING_HOST=172.18.0.1
-WEBMAIL_URL=/roundcubemail
-APP_URL=https://{{ host }}
-ASSET_URL=https://{{ host }}
-
-DB_HOST=mariadb
-REDIS_HOST=redis
-IMAP_URI=ssl://kolab:11993
-LDAP_HOSTS=kolab
-
-MOLLIE_KEY=
-STRIPE_KEY=
-STRIPE_PUBLIC_KEY=
-STRIPE_WEBHOOK_SECRET=
-
-OCTANE_HTTP_HOST={{ host }}
-OPENEXCHANGERATES_API_KEY={{ openexchangerates_api_key }}
-FIREBASE_API_KEY={{ firebase_api_key }}
-
-#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
-PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee
-PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf
-
-#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
-PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb
-PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz
-
-APP_TENANT_ID=42
-APP_PASSPHRASE=simple123
-
-MAIL_DRIVER=log
-
-KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert
-KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem
-KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key
-
-PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert
-PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key
diff --git a/config.demo/docker-compose.override.yml b/config.demo/docker-compose.override.yml
new file mode 100644
index 00000000..bf93c1d5
--- /dev/null
+++ b/config.demo/docker-compose.override.yml
@@ -0,0 +1,47 @@
+version: '3'
+services:
+  haproxy:
+    depends_on:
+      proxy:
+        condition: service_healthy
+  proxy:
+    depends_on:
+      kolab:
+        condition: service_healthy
+      webapp:
+        condition: service_healthy
+    build:
+      context: ./docker/proxy/
+      args:
+        APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err}
+        SSL_CERTIFICATE: ${PROXY_SSL_CERTIFICATE:?err}
+        SSL_CERTIFICATE_KEY: ${PROXY_SSL_CERTIFICATE_KEY:?err}
+    healthcheck:
+      interval: 10s
+      test: "kill -0 $$(cat /run/nginx.pid)"
+      timeout: 5s
+      retries: 30
+    container_name: kolab-proxy
+    hostname: proxy
+    image: kolab-proxy
+    extra_hosts:
+      - "meet:${MEET_LISTENING_HOST}"
+    networks:
+      kolab:
+        ipv4_address: 172.18.0.7
+    tmpfs:
+      - /run
+      - /tmp
+      - /var/run
+      - /var/tmp
+    tty: true
+    volumes:
+      - ./docker/certs/:/etc/certs/:ro
+      - /etc/letsencrypt/:/etc/letsencrypt/:ro
+    ports:
+      # - "80:80"
+      - "443:443"
+      - "465:465"
+      - "587:587"
+      - "143:143"
+      - "993:993"
diff --git a/src/.env.example b/config.demo/src/.env
similarity index 81%
copy from src/.env.example
copy to config.demo/src/.env
index b093a100..91d4030c 100644
--- a/src/.env.example
+++ b/config.demo/src/.env
@@ -1,185 +1,206 @@
 APP_NAME=Kolab
 APP_ENV=local
 APP_KEY=
 APP_DEBUG=true
 APP_URL=https://kolab.local
-#APP_PASSPHRASE=
+APP_PASSPHRASE=simple123
 APP_PUBLIC_URL=https://kolab.local
 APP_DOMAIN=kolab.local
 APP_WEBSITE_DOMAIN=kolab.local
 APP_THEME=default
 APP_TENANT_ID=5
 APP_LOCALE=en
 APP_LOCALES=
 
 APP_WITH_ADMIN=1
 APP_WITH_RESELLER=1
 APP_WITH_SERVICES=1
 APP_WITH_FILES=1
 
 APP_LDAP=1
+APP_IMAP=0
 
 APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';"
 APP_HEADER_XFO=sameorigin
 
 SIGNUP_LIMIT_EMAIL=0
 SIGNUP_LIMIT_IP=0
 
 ASSET_URL=https://kolab.local
 
 WEBMAIL_URL=/roundcubemail/
 SUPPORT_URL=/support
 SUPPORT_EMAIL=
 
 LOG_CHANNEL=stack
 LOG_SLOW_REQUESTS=5
 LOG_DEPRECATIONS_CHANNEL=null
 LOG_LEVEL=debug
 
 DB_CONNECTION=mysql
 DB_DATABASE=kolabdev
 DB_HOST=mariadb
 DB_PASSWORD=kolab
 DB_PORT=3306
 DB_USERNAME=kolabdev
 
 BROADCAST_DRIVER=redis
 CACHE_DRIVER=redis
 
 QUEUE_CONNECTION=redis
 
 SESSION_DRIVER=file
 SESSION_LIFETIME=120
 
 OPENEXCHANGERATES_API_KEY="from openexchangerates.org"
 
-MFA_DSN=mysql://roundcube:Welcome2KolabSystems@mariadb/roundcube
+MFA_DSN=mysql://roundcube:kolab@mariadb/roundcube
 MFA_TOTP_DIGITS=6
 MFA_TOTP_INTERVAL=30
 MFA_TOTP_DIGEST=sha1
 
 IMAP_URI=ssl://kolab:11993
 IMAP_HOST=172.18.0.5
 IMAP_ADMIN_LOGIN=cyrus-admin
 IMAP_ADMIN_PASSWORD=Welcome2KolabSystems
 IMAP_VERIFY_HOST=false
 IMAP_VERIFY_PEER=false
 
 LDAP_BASE_DN="dc=mgmt,dc=com"
 LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com"
 LDAP_HOSTS=kolab
 LDAP_PORT=389
 LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com"
 LDAP_SERVICE_BIND_PW="Welcome2KolabSystems"
 LDAP_USE_SSL=false
 LDAP_USE_TLS=false
 
 # Administrative
 LDAP_ADMIN_BIND_DN="cn=Directory Manager"
 LDAP_ADMIN_BIND_PW="Welcome2KolabSystems"
 LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com"
 
 # Hosted (public registration)
 LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com"
 LDAP_HOSTED_BIND_PW="Welcome2KolabSystems"
 LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com"
 
 COTURN_PUBLIC_IP='172.18.0.1'
 COTURN_STATIC_SECRET="Welcome2KolabSystems"
 
 MEET_WEBHOOK_TOKEN=Welcome2KolabSystems
 MEET_SERVER_TOKEN=Welcome2KolabSystems
 MEET_SERVER_URLS=https://kolab.local/meetmedia/api/
 MEET_SERVER_VERIFY_TLS=false
 
 MEET_WEBRTC_LISTEN_IP='172.18.0.1'
 MEET_PUBLIC_DOMAIN=kolab.local
 MEET_TURN_SERVER='turn:172.18.0.1:3478'
 MEET_LISTENING_HOST=172.18.0.1
 
 
 PGP_ENABLE=true
 PGP_BINARY=/usr/bin/gpg
 PGP_AGENT=/usr/bin/gpg-agent
 PGP_GPGCONF=/usr/bin/gpgconf
 PGP_LENGTH=
 
 # Set these to IP addresses you serve WOAT with.
 # Have the domain owner point _woat.<hosted-domain> NS RRs refer to ns0{1,2}.<provider-domain>
 WOAT_NS1=ns01.domain.tld
 WOAT_NS2=ns02.domain.tld
 
 REDIS_HOST=redis
 REDIS_PASSWORD=null
 REDIS_PORT=6379
 
-OCTANE_HTTP_HOST=127.0.0.1
+OCTANE_HTTP_HOST=0.0.0.0
 SWOOLE_PACKAGE_MAX_LENGTH=10485760
 
 PAYMENT_PROVIDER=
 MOLLIE_KEY=
 STRIPE_KEY=
 STRIPE_PUBLIC_KEY=
 STRIPE_WEBHOOK_SECRET=
 
 MAIL_DRIVER=log
 MAIL_MAILER=smtp
 MAIL_HOST=smtp.mailtrap.io
 MAIL_PORT=2525
 MAIL_USERNAME=null
 MAIL_PASSWORD=null
 MAIL_ENCRYPTION=null
 MAIL_FROM_ADDRESS="noreply@example.com"
 MAIL_FROM_NAME="Example.com"
 MAIL_REPLYTO_ADDRESS="replyto@example.com"
 MAIL_REPLYTO_NAME=null
 
 DNS_TTL=3600
 DNS_SPF="v=spf1 mx -all"
 DNS_STATIC="%s.    MX  10 ext-mx01.mykolab.com."
 DNS_COPY_FROM=null
 
 AWS_ACCESS_KEY_ID=
 AWS_SECRET_ACCESS_KEY=
 AWS_DEFAULT_REGION=us-east-1
 AWS_BUCKET=
 AWS_USE_PATH_STYLE_ENDPOINT=false
 
 PUSHER_APP_ID=
 PUSHER_APP_KEY=
 PUSHER_APP_SECRET=
 PUSHER_APP_CLUSTER=mt1
 
 MIX_ASSET_PATH='/'
 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
 MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
 
 # Generate with ./artisan passport:client --password
 #PASSPORT_PROXY_OAUTH_CLIENT_ID=
 #PASSPORT_PROXY_OAUTH_CLIENT_SECRET=
 
 PASSPORT_PRIVATE_KEY=
 PASSPORT_PUBLIC_KEY=
 
 PASSWORD_POLICY=
 
 COMPANY_NAME=
 COMPANY_ADDRESS=
 COMPANY_DETAILS=
 COMPANY_EMAIL=
 COMPANY_LOGO=
 COMPANY_FOOTER=
 
 VAT_COUNTRIES=CH,LI
 VAT_RATE=7.7
 
 KB_ACCOUNT_DELETE=
 KB_ACCOUNT_SUSPENDED=
 KB_PAYMENT_SYSTEM=
 
 KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert
 KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem
 KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key
 
 PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert
 PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key
+
+APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E=
+COTURN_STATIC_SECRET=uzYguvIl9tpZFMuQOE78DpOi6Jc7VFSD0UAnvgMsg5n4e74MgIf6vQvbc6LWzZjz
+
+MOLLIE_KEY="from mollie"
+STRIPE_KEY="from stripe"
+STRIPE_PUBLIC_KEY="from stripe"
+STRIPE_WEBHOOK_SECRET="from stripe"
+
+OX_API_KEY="from openexchange"
+FIREBASE_API_KEY="from firebase"
+
+#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
+PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee
+PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf
+
+#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
+PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb
+PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz
+
diff --git a/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php b/config.demo/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
similarity index 100%
rename from src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
rename to config.demo/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
diff --git a/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php b/config.demo/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php
similarity index 100%
rename from src/database/migrations/2021_02_19_100000_transaction_amount_fix.php
rename to config.demo/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php
diff --git a/src/database/migrations/2021_12_15_100000_rename_beta_skus.php b/config.demo/src/database/migrations/2021_12_15_100000_rename_beta_skus.php
similarity index 100%
rename from src/database/migrations/2021_12_15_100000_rename_beta_skus.php
rename to config.demo/src/database/migrations/2021_12_15_100000_rename_beta_skus.php
diff --git a/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php b/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php
new file mode 100644
index 00000000..0cabaf4f
--- /dev/null
+++ b/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php
@@ -0,0 +1,57 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        // Create the new SKUs
+        \App\Sku::create([
+                'title' => 'group-room',
+                'name' => 'Group conference room',
+                'description' => 'Shareable audio & video conference room',
+                'cost' => 0,
+                'units_free' => 0,
+                'period' => 'monthly',
+                'handler_class' => 'App\Handlers\GroupRoom',
+                'active' => true,
+        ]);
+
+        \App\Sku::create([
+                'title' => 'room',
+                'name' => 'Standard conference room',
+                'description' => 'Audio & video conference room',
+                'cost' => 0,
+                'units_free' => 0,
+                'period' => 'monthly',
+                'handler_class' => 'App\Handlers\Room',
+                'active' => true,
+        ]);
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        \App\Sku::where('title', 'room')->delete();
+        \App\Sku::where('title', 'group-room')->delete();
+
+        \App\Sku::create([
+                'title' => 'meet',
+                'name' => 'Voice & Video Conferencing (public beta)',
+                'description' => 'Video conferencing tool',
+                'cost' => 0,
+                'units_free' => 0,
+                'period' => 'monthly',
+                'handler_class' => 'App\Handlers\Meet',
+                'active' => true,
+        ]);
+    }
+};
diff --git a/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php b/config.demo/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php
similarity index 100%
rename from src/database/migrations/2022_07_08_100000_fix_group_sku_name.php
rename to config.demo/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php
diff --git a/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php b/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php
new file mode 100644
index 00000000..4453990d
--- /dev/null
+++ b/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php
@@ -0,0 +1,26 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Support\Facades\DB;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        DB::table('plans')->update(['free_months' => 1]);
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+    }
+};
diff --git a/config.demo/src/database/seeds/DatabaseSeeder.php b/config.demo/src/database/seeds/DatabaseSeeder.php
new file mode 100644
index 00000000..cebfef72
--- /dev/null
+++ b/config.demo/src/database/seeds/DatabaseSeeder.php
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Seeder;
+use Database\Seeds;
+
+// phpcs:ignore
+class DatabaseSeeder extends Seeder
+{
+    /**
+     * Seed the application's database.
+     *
+     * @return void
+     */
+    public function run()
+    {
+        $this->call([
+            Seeds\IP4NetSeeder::class,
+            Seeds\TenantSeeder::class,
+            Seeds\DiscountSeeder::class,
+            Seeds\DomainSeeder::class,
+            Seeds\SkuSeeder::class,
+            Seeds\PackageSeeder::class,
+            Seeds\PlanSeeder::class,
+            Seeds\PowerDNSSeeder::class,
+            Seeds\UserSeeder::class,
+            Seeds\OauthClientSeeder::class,
+            Seeds\ResourceSeeder::class,
+            Seeds\SharedFolderSeeder::class,
+            Seeds\MeetRoomSeeder::class,
+        ]);
+    }
+}
diff --git a/src/database/seeds/local/DiscountSeeder.php b/config.demo/src/database/seeds/DiscountSeeder.php
similarity index 97%
rename from src/database/seeds/local/DiscountSeeder.php
rename to config.demo/src/database/seeds/DiscountSeeder.php
index 1d22bdd5..23633db2 100644
--- a/src/database/seeds/local/DiscountSeeder.php
+++ b/config.demo/src/database/seeds/DiscountSeeder.php
@@ -1,58 +1,58 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Discount;
 use Illuminate\Database\Seeder;
 
 class DiscountSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         Discount::create(
             [
                 'description' => 'Free Account',
                 'discount' => 100,
                 'active' => true,
             ]
         );
 
         Discount::create(
             [
                 'description' => 'Student or Educational Institution',
                 'discount' => 30,
                 'active' => true,
             ]
         );
 
         Discount::create(
             [
                 'description' => 'Test voucher',
                 'discount' => 10,
                 'active' => true,
                 'code' => 'TEST',
             ]
         );
 
         // We're running in reseller mode, add a sample discount
         $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get();
 
         foreach ($tenants as $tenant) {
             $discount = Discount::create(
                 [
                     'description' => "Sample Discount by Reseller '{$tenant->title}'",
                     'discount' => 10,
                     'active' => true,
                 ]
             );
 
             $discount->tenant_id = $tenant->id;
             $discount->save();
         }
     }
 }
diff --git a/src/database/seeds/local/DomainSeeder.php b/config.demo/src/database/seeds/DomainSeeder.php
similarity index 98%
rename from src/database/seeds/local/DomainSeeder.php
rename to config.demo/src/database/seeds/DomainSeeder.php
index 777c3c4b..c8834b03 100644
--- a/src/database/seeds/local/DomainSeeder.php
+++ b/config.demo/src/database/seeds/DomainSeeder.php
@@ -1,86 +1,86 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Domain;
 use Illuminate\Database\Seeder;
 
 class DomainSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $domains = [
             "kolabnow.com",
             "mykolab.com",
             "attorneymail.ch",
             "barmail.ch",
             "collaborative.li",
             "diplomail.ch",
             "freedommail.ch",
             "groupoffice.ch",
             "journalistmail.ch",
             "legalprivilege.ch",
             "libertymail.co",
             "libertymail.net"
         ];
 
         foreach ($domains as $domain) {
             Domain::create(
                 [
                     'namespace' => $domain,
                     'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
                     'type' => Domain::TYPE_PUBLIC,
                 ]
             );
         }
 
         if (!in_array(\config('app.domain'), $domains)) {
             Domain::create(
                 [
                     'namespace' => \config('app.domain'),
                     'status' => DOMAIN::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
                     'type' => Domain::TYPE_PUBLIC,
                 ]
             );
         }
 
         $domains = [
             'example.com',
             'example.net',
             'example.org'
         ];
 
         foreach ($domains as $domain) {
             Domain::create(
                 [
                     'namespace' => $domain,
                     'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
                     'type' => Domain::TYPE_EXTERNAL,
                 ]
             );
         }
 
         // We're running in reseller mode, add a sample discount
         $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get();
 
         foreach ($tenants as $tenant) {
             $domainNamespace = strtolower(str_replace(' ', '-', $tenant->title)) . '.dev-local';
 
             $domain = Domain::create(
                 [
                     'namespace' => $domainNamespace,
                     'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
                     'type' => Domain::TYPE_PUBLIC,
                 ]
             );
 
             $domain->tenant_id = $tenant->id;
             $domain->save();
         }
     }
 }
diff --git a/src/database/seeds/local/IP4NetSeeder.php b/config.demo/src/database/seeds/IP4NetSeeder.php
similarity index 95%
rename from src/database/seeds/local/IP4NetSeeder.php
rename to config.demo/src/database/seeds/IP4NetSeeder.php
index 7e46e52b..b6ff03d1 100644
--- a/src/database/seeds/local/IP4NetSeeder.php
+++ b/config.demo/src/database/seeds/IP4NetSeeder.php
@@ -1,31 +1,31 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\IP4Net;
 use Illuminate\Database\Seeder;
 
 class IP4NetSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         // necessary for tests
         IP4Net::create(
             [
                 'rir_name' => 'ripencc',
                 'net_number' => '212.103.64.0',
                 'net_mask' => 19,
                 'net_broadcast' => '212.103.95.255',
                 'country' => 'CH',
                 'serial' => 1,
                 'created_at' => '1999-02-05 00:00:00',
                 'updated_at' => '2021-07-30 08:49:30'
             ]
         );
     }
 }
diff --git a/src/database/seeds/local/MeetRoomSeeder.php b/config.demo/src/database/seeds/MeetRoomSeeder.php
similarity index 96%
rename from src/database/seeds/local/MeetRoomSeeder.php
rename to config.demo/src/database/seeds/MeetRoomSeeder.php
index b38bb27a..d09b9189 100644
--- a/src/database/seeds/local/MeetRoomSeeder.php
+++ b/config.demo/src/database/seeds/MeetRoomSeeder.php
@@ -1,39 +1,39 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use Illuminate\Database\Seeder;
 
 class MeetRoomSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $john = \App\User::where('email', 'john@kolab.org')->first();
         $wallet = $john->wallets()->first();
 
         $rooms = [
             [
                 'name' => 'john',
                 'description' => "Standard room"
             ],
             [
                 'name' => 'shared',
                 'description' => "Shared room"
             ]
         ];
 
         foreach ($rooms as $idx => $room) {
             $room = \App\Meet\Room::create($room);
             $rooms[$idx] = $room;
         }
 
         $rooms[0]->assignToWallet($wallet, 'room');
         $rooms[1]->assignToWallet($wallet, 'group-room');
         $rooms[1]->setConfig(['acl' => 'jack@kolab.org, full']);
     }
 }
diff --git a/src/database/seeds/local/OauthClientSeeder.php b/config.demo/src/database/seeds/OauthClientSeeder.php
similarity index 96%
rename from src/database/seeds/local/OauthClientSeeder.php
rename to config.demo/src/database/seeds/OauthClientSeeder.php
index 5a608406..db20521f 100644
--- a/src/database/seeds/local/OauthClientSeeder.php
+++ b/config.demo/src/database/seeds/OauthClientSeeder.php
@@ -1,33 +1,33 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use Laravel\Passport\Passport;
 use Illuminate\Database\Seeder;
 
 class OauthClientSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * This emulates './artisan passport:client --password --name="Kolab Password Grant Client" --provider=users'
      *
      * @return void
      */
     public function run()
     {
         $client = Passport::client()->forceFill([
             'user_id' => null,
             'name' => "Kolab Password Grant Client",
             'secret' => \config('auth.proxy.client_secret'),
             'provider' => 'users',
             'redirect' => 'https://' . \config('app.website_domain'),
             'personal_access_client' => 0,
             'password_client' => 1,
             'revoked' => false,
         ]);
 
         $client->id = \config('auth.proxy.client_id');
         $client->save();
     }
 }
diff --git a/src/database/seeds/local/PackageSeeder.php b/config.demo/src/database/seeds/PackageSeeder.php
similarity index 99%
rename from src/database/seeds/local/PackageSeeder.php
rename to config.demo/src/database/seeds/PackageSeeder.php
index 78ecdf9c..7ef80906 100644
--- a/src/database/seeds/local/PackageSeeder.php
+++ b/config.demo/src/database/seeds/PackageSeeder.php
@@ -1,166 +1,166 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Package;
 use App\Sku;
 use Illuminate\Database\Seeder;
 
 class PackageSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $skuDomain = Sku::where(['title' => 'domain-hosting', 'tenant_id' => \config('app.tenant_id')])->first();
         $skuGroupware = Sku::where(['title' => 'groupware', 'tenant_id' => \config('app.tenant_id')])->first();
         $skuMailbox = Sku::where(['title' => 'mailbox', 'tenant_id' => \config('app.tenant_id')])->first();
         $skuStorage = Sku::where(['title' => 'storage', 'tenant_id' => \config('app.tenant_id')])->first();
 
         $package = Package::create(
             [
                 'title' => 'kolab',
                 'name' => 'Groupware Account',
                 'description' => 'A fully functional groupware account.',
                 'discount_rate' => 0,
             ]
         );
 
         $skus = [
             $skuMailbox,
             $skuGroupware,
             $skuStorage
         ];
 
         $package->skus()->saveMany($skus);
 
         // This package contains 2 units of the storage SKU, which just so happens to also
         // be the number of SKU free units.
         $package->skus()->updateExistingPivot(
             $skuStorage,
             ['qty' => 5],
             false
         );
 
         $package = Package::create(
             [
                 'title' => 'lite',
                 'name' => 'Lite Account',
                 'description' => 'Just mail and no more.',
                 'discount_rate' => 0,
             ]
         );
 
         $skus = [
             $skuMailbox,
             $skuStorage
         ];
 
         $package->skus()->saveMany($skus);
 
         $package->skus()->updateExistingPivot(
             $skuStorage,
             ['qty' => 5],
             false
         );
 
         $package = Package::create(
             [
                 'title' => 'domain-hosting',
                 'name' => 'Domain Hosting',
                 'description' => 'Use your own, existing domain.',
                 'discount_rate' => 0,
             ]
         );
 
         $skus = [
             $skuDomain
         ];
 
         $package->skus()->saveMany($skus);
 
         // We're running in reseller mode, add a sample discount
         $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get();
 
         foreach ($tenants as $tenant) {
             $skuDomain = Sku::where(['title' => 'domain-hosting', 'tenant_id' => $tenant->id])->first();
             $skuGroupware = Sku::where(['title' => 'groupware', 'tenant_id' => $tenant->id])->first();
             $skuMailbox = Sku::where(['title' => 'mailbox', 'tenant_id' => $tenant->id])->first();
             $skuStorage = Sku::where(['title' => 'storage', 'tenant_id' => $tenant->id])->first();
 
             $package = Package::create(
                 [
                     'title' => 'kolab',
                     'name' => 'Groupware Account',
                     'description' => 'A fully functional groupware account.',
                     'discount_rate' => 0
                 ]
             );
 
             $package->tenant_id = $tenant->id;
             $package->save();
 
             $skus = [
                 $skuMailbox,
                 $skuGroupware,
                 $skuStorage
             ];
 
             $package->skus()->saveMany($skus);
 
             // This package contains 2 units of the storage SKU, which just so happens to also
             // be the number of SKU free units.
             $package->skus()->updateExistingPivot(
                 $skuStorage,
                 ['qty' => 5],
                 false
             );
 
             $package = Package::create(
                 [
                     'title' => 'lite',
                     'name' => 'Lite Account',
                     'description' => 'Just mail and no more.',
                     'discount_rate' => 0
                 ]
             );
 
             $package->tenant_id = $tenant->id;
             $package->save();
 
             $skus = [
                 $skuMailbox,
                 $skuStorage
             ];
 
             $package->skus()->saveMany($skus);
 
             $package->skus()->updateExistingPivot(
                 $skuStorage,
                 ['qty' => 5],
                 false
             );
 
             $package = Package::create(
                 [
                     'title' => 'domain-hosting',
                     'name' => 'Domain Hosting',
                     'description' => 'Use your own, existing domain.',
                     'discount_rate' => 0
                 ]
             );
 
             $package->tenant_id = $tenant->id;
             $package->save();
 
             $skus = [
                 $skuDomain
             ];
 
             $package->skus()->saveMany($skus);
         }
     }
 }
diff --git a/src/database/seeds/local/PlanSeeder.php b/config.demo/src/database/seeds/PlanSeeder.php
similarity index 99%
rename from src/database/seeds/local/PlanSeeder.php
rename to config.demo/src/database/seeds/PlanSeeder.php
index a21530f5..d420dc42 100644
--- a/src/database/seeds/local/PlanSeeder.php
+++ b/config.demo/src/database/seeds/PlanSeeder.php
@@ -1,145 +1,145 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Package;
 use App\Plan;
 use Illuminate\Database\Seeder;
 
 class PlanSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $description = <<<'EOD'
 <p>Everything you need to get started or try Kolab Now, including:</p>
 <ul>
     <li>Perfect for anyone wanting to move to Kolab Now</li>
     <li>Suite of online apps: Secure email, calendar, address book, files and more</li>
     <li>Access for anywhere: Sync all your devices to your Kolab Now account</li>
     <li>Secure hosting: Managed right here on our own servers in Switzerland </li>
     <li>Start protecting your data today, no ads, no crawling, no compromise</li>
     <li>An ideal replacement for services like Gmail, Office 365, etc…</li>
 </ul>
 EOD;
 
         $plan = Plan::create(
             [
                 'title' => 'individual',
                 'name' => 'Individual Account',
                 'description' => $description,
                 'free_months' => 1,
                 'discount_qty' => 0,
                 'discount_rate' => 0
             ]
         );
 
         $packages = [
             Package::where(['title' => 'kolab', 'tenant_id' => \config('app.tenant_id')])->first()
         ];
 
         $plan->packages()->saveMany($packages);
 
         $description = <<<'EOD'
 <p>All the features of the Individual Account, with the following extras:</p>
 <ul>
     <li>Perfect for anyone wanting to move a group or small business to Kolab Now</li>
     <li>Recommended to support users from 1 to 100</li>
     <li>Use your own personal domains with Kolab Now</li>
     <li>Manage and add users through our online admin area</li>
     <li>Flexible pricing based on user count</li>
 </ul>
 EOD;
 
         $plan = Plan::create(
             [
                 'title' => 'group',
                 'name' => 'Group Account',
                 'description' => $description,
                 'free_months' => 1,
                 'discount_qty' => 0,
                 'discount_rate' => 0
             ]
         );
 
         $packages = [
             Package::where(['title' => 'domain-hosting', 'tenant_id' => \config('app.tenant_id')])->first(),
             Package::where(['title' => 'kolab', 'tenant_id' => \config('app.tenant_id')])->first()
         ];
 
         $plan->packages()->saveMany($packages);
 
         // We're running in reseller mode, add a sample discount
         $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get();
 
         foreach ($tenants as $tenant) {
             $description = <<<'EOD'
 <p>Everything you need to get started or try Kolab Now, including:</p>
 <ul>
     <li>Perfect for anyone wanting to move to Kolab Now</li>
     <li>Suite of online apps: Secure email, calendar, address book, files and more</li>
     <li>Access for anywhere: Sync all your devices to your Kolab Now account</li>
     <li>Secure hosting: Managed right here on our own servers in Switzerland </li>
     <li>Start protecting your data today, no ads, no crawling, no compromise</li>
     <li>An ideal replacement for services like Gmail, Office 365, etc…</li>
 </ul>
 EOD;
 
             $plan = Plan::create(
                 [
                     'title' => 'individual',
                     'name' => 'Individual Account',
                     'free_months' => 1,
                     'description' => $description,
                     'discount_qty' => 0,
                     'discount_rate' => 0
                 ]
             );
 
             $plan->tenant_id = $tenant->id;
             $plan->save();
 
             $packages = [
                 Package::where(['title' => 'kolab', 'tenant_id' => $tenant->id])->first()
             ];
 
             $plan->packages()->saveMany($packages);
 
             $description = <<<'EOD'
 <p>All the features of the Individual Account, with the following extras:</p>
 <ul>
     <li>Perfect for anyone wanting to move a group or small business to Kolab Now</li>
     <li>Recommended to support users from 1 to 100</li>
     <li>Use your own personal domains with Kolab Now</li>
     <li>Manage and add users through our online admin area</li>
     <li>Flexible pricing based on user count</li>
 </ul>
 EOD;
 
             $plan = Plan::create(
                 [
                     'title' => 'group',
                     'name' => 'Group Account',
                     'description' => $description,
                     'free_months' => 1,
                     'discount_qty' => 0,
                     'discount_rate' => 0
                 ]
             );
 
             $plan->tenant_id = $tenant->id;
             $plan->save();
 
             $packages = [
                 Package::where(['title' => 'domain-hosting', 'tenant_id' => $tenant->id])->first(),
                 Package::where(['title' => 'kolab', 'tenant_id' => $tenant->id])->first()
             ];
 
             $plan->packages()->saveMany($packages);
         }
     }
 }
diff --git a/src/database/seeds/local/PowerDNSSeeder.php b/config.demo/src/database/seeds/PowerDNSSeeder.php
similarity index 96%
rename from src/database/seeds/local/PowerDNSSeeder.php
rename to config.demo/src/database/seeds/PowerDNSSeeder.php
index 540b6184..d124fea3 100644
--- a/src/database/seeds/local/PowerDNSSeeder.php
+++ b/config.demo/src/database/seeds/PowerDNSSeeder.php
@@ -1,43 +1,43 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Domain;
 use Illuminate\Database\Seeder;
 
 class PowerDNSSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $domain = \App\PowerDNS\Domain::create(
             [
                 'name' => '_woat.' . \config('app.domain')
             ]
         );
 
         /*
         $domainLuaSetting = \App\PowerDNS\DomainSetting::where(
             ['domain_id' => $domain->id, 'kind' => 'ENABLE-LUA-RECORDS']
         )->first();
 
         $domainLuaSetting->{'content'} = "1";
         $domainLuaSetting->save();
         */
 
         /*
         \App\PowerDNS\Record::create(
             [
                 'domain_id' => $domain->id,
                 'name' => $domain->{'name'},
                 'type' => 'A',
                 'content' => '10.4.2.23'
             ]
         );
         */
     }
 }
diff --git a/src/database/seeds/local/ResourceSeeder.php b/config.demo/src/database/seeds/ResourceSeeder.php
similarity index 95%
rename from src/database/seeds/local/ResourceSeeder.php
rename to config.demo/src/database/seeds/ResourceSeeder.php
index 36061c44..71778655 100644
--- a/src/database/seeds/local/ResourceSeeder.php
+++ b/config.demo/src/database/seeds/ResourceSeeder.php
@@ -1,33 +1,33 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Resource;
 use App\User;
 use Illuminate\Database\Seeder;
 
 class ResourceSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $john = User::where('email', 'john@kolab.org')->first();
         $wallet = $john->wallets()->first();
 
         $resource = Resource::create([
                 'name' => 'Conference Room #1',
                 'email' => 'resource-test1@kolab.org',
         ]);
         $resource->assignToWallet($wallet);
 
         $resource = Resource::create([
                 'name' => 'Conference Room #2',
                 'email' => 'resource-test2@kolab.org',
         ]);
         $resource->assignToWallet($wallet);
     }
 }
diff --git a/src/database/seeds/local/SharedFolderSeeder.php b/config.demo/src/database/seeds/SharedFolderSeeder.php
similarity index 96%
rename from src/database/seeds/local/SharedFolderSeeder.php
rename to config.demo/src/database/seeds/SharedFolderSeeder.php
index fdb9ed7c..c697ada7 100644
--- a/src/database/seeds/local/SharedFolderSeeder.php
+++ b/config.demo/src/database/seeds/SharedFolderSeeder.php
@@ -1,35 +1,35 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\SharedFolder;
 use App\User;
 use Illuminate\Database\Seeder;
 
 class SharedFolderSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $john = User::where('email', 'john@kolab.org')->first();
         $wallet = $john->wallets()->first();
 
         $folder = SharedFolder::create([
                 'name' => 'Calendar',
                 'email' => 'folder-event@kolab.org',
                 'type' => 'event',
         ]);
         $folder->assignToWallet($wallet);
 
         $folder = SharedFolder::create([
                 'name' => 'Contacts',
                 'email' => 'folder-contact@kolab.org',
                 'type' => 'contact',
         ]);
         $folder->assignToWallet($wallet);
     }
 }
diff --git a/src/database/seeds/local/SkuSeeder.php b/config.demo/src/database/seeds/SkuSeeder.php
similarity index 99%
rename from src/database/seeds/local/SkuSeeder.php
rename to config.demo/src/database/seeds/SkuSeeder.php
index 57f325a9..de661d07 100644
--- a/src/database/seeds/local/SkuSeeder.php
+++ b/config.demo/src/database/seeds/SkuSeeder.php
@@ -1,250 +1,250 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Sku;
 use Illuminate\Database\Seeder;
 
 class SkuSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $skus = [
             [
                 'title' => 'mailbox',
                 'name' => 'User Mailbox',
                 'description' => 'Just a mailbox',
                 'cost' => 500,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Mailbox',
                 'active' => true,
             ],
             [
                 'title' => 'domain',
                 'name' => 'Hosted Domain',
                 'description' => 'Somewhere to place a mailbox',
                 'cost' => 100,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Domain',
                 'active' => false,
             ],
             [
                 'title' => 'domain-registration',
                 'name' => 'Domain Registration',
                 'description' => 'Register a domain with us',
                 'cost' => 101,
                 'period' => 'yearly',
                 'handler_class' => 'App\Handlers\DomainRegistration',
                 'active' => false,
             ],
             [
                 'title' => 'domain-hosting',
                 'name' => 'External Domain',
                 'description' => 'Host a domain that is externally registered',
                 'cost' => 100,
                 'units_free' => 1,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\DomainHosting',
                 'active' => true,
             ],
             [
                 'title' => 'domain-relay',
                 'name' => 'Domain Relay',
                 'description' => 'A domain you host at home, for which we relay email',
                 'cost' => 103,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\DomainRelay',
                 'active' => false,
             ],
             [
                 'title' => 'storage',
                 'name' => 'Storage Quota',
                 'description' => 'Some wiggle room',
                 'cost' => 25,
                 'units_free' => 5,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Storage',
                 'active' => true,
             ],
             [
                 'title' => 'groupware',
                 'name' => 'Groupware Features',
                 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.',
                 'cost' => 490,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Groupware',
                 'active' => true,
             ],
             [
                 'title' => 'resource',
                 'name' => 'Resource',
                 'description' => 'Reservation taker',
                 'cost' => 101,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Resource',
                 'active' => true,
             ],
             [
                 'title' => 'shared-folder',
                 'name' => 'Shared Folder',
                 'description' => 'A shared folder',
                 'cost' => 89,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\SharedFolder',
                 'active' => true,
             ],
             [
                 'title' => '2fa',
                 'name' => '2-Factor Authentication',
                 'description' => 'Two factor authentication for webmail and administration panel',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Auth2F',
                 'active' => true,
             ],
             [
                 'title' => 'activesync',
                 'name' => 'Activesync',
                 'description' => 'Mobile synchronization',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Activesync',
                 'active' => true,
             ],
             [
                 'title' => 'beta',
                 'name' => 'Private Beta (invitation only)',
                 'description' => 'Access to the private beta program features',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Beta',
                 'active' => false,
             ],
             [
                 'title' => 'group',
                 'name' => 'Distribution list',
                 'description' => 'Mail distribution list',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Group',
                 'active' => true,
             ],
             [
                 'title' => 'group-room',
                 'name' => 'Group conference room',
                 'description' => 'Shareable audio & video conference room',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\GroupRoom',
                 'active' => true,
             ],
             [
                 'title' => 'room',
                 'name' => 'Standard conference room',
                 'description' => 'Audio & video conference room',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Room',
                 'active' => true,
             ],
         ];
 
         foreach ($skus as $sku) {
             // Check existence because migration might have added this already
             if (!Sku::where('title', $sku['title'])->where('tenant_id', \config('app.tenant_id'))->first()) {
                 Sku::create($sku);
             }
         }
 
         $skus = [
             [
                 'title' => 'mailbox',
                 'name' => 'User Mailbox',
                 'description' => 'Just a mailbox',
                 'cost' => 500,
                 'fee' => 333,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Mailbox',
                 'active' => true,
             ],
             [
                 'title' => 'storage',
                 'name' => 'Storage Quota',
                 'description' => 'Some wiggle room',
                 'cost' => 25,
                 'fee' => 16,
                 'units_free' => 5,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Storage',
                 'active' => true,
             ],
             [
                 'title' => 'domain-hosting',
                 'name' => 'External Domain',
                 'description' => 'Host a domain that is externally registered',
                 'cost' => 100,
                 'fee' => 66,
                 'units_free' => 1,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\DomainHosting',
                 'active' => true,
             ],
             [
                 'title' => 'groupware',
                 'name' => 'Groupware Features',
                 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.',
                 'cost' => 490,
                 'fee' => 327,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Groupware',
                 'active' => true,
             ],
             [
                 'title' => '2fa',
                 'name' => '2-Factor Authentication',
                 'description' => 'Two factor authentication for webmail and administration panel',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Auth2F',
                 'active' => true,
             ],
             [
                 'title' => 'activesync',
                 'name' => 'Activesync',
                 'description' => 'Mobile synchronization',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Activesync',
                 'active' => true,
             ],
         ];
 
         // for tenants that are not the configured tenant id
         $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get();
 
         foreach ($tenants as $tenant) {
             foreach ($skus as $sku) {
                 $sku = Sku::create($sku);
                 $sku->tenant_id = $tenant->id;
                 $sku->save();
             }
         }
     }
 }
diff --git a/src/database/seeds/local/TenantSeeder.php b/config.demo/src/database/seeds/TenantSeeder.php
similarity index 96%
rename from src/database/seeds/local/TenantSeeder.php
rename to config.demo/src/database/seeds/TenantSeeder.php
index 0c8c18f4..57fcb168 100644
--- a/src/database/seeds/local/TenantSeeder.php
+++ b/config.demo/src/database/seeds/TenantSeeder.php
@@ -1,37 +1,37 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Tenant;
 use Illuminate\Database\Seeder;
 
 class TenantSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         if (\config('app.tenant_id')) {
             $tenant = Tenant::where(['title' => 'Kolab Now'])->first();
 
             if (!$tenant) {
                 Tenant::create(['title' => 'Kolab Now']);
             }
 
             $tenant = Tenant::where(['title' => 'Sample Tenant'])->first();
 
             if (!$tenant) {
                 $tenant = Tenant::create(['title' => 'Sample Tenant']);
             }
 
             $tenant = Tenant::where(['title' => 'kanarip.ch'])->first();
 
             if (!$tenant) {
                 $tenant = Tenant::create(['title' => 'kanarip.ch']);
             }
         }
     }
 }
diff --git a/src/database/seeds/local/UserSeeder.php b/config.demo/src/database/seeds/UserSeeder.php
similarity index 99%
rename from src/database/seeds/local/UserSeeder.php
rename to config.demo/src/database/seeds/UserSeeder.php
index b93db82f..8812f910 100644
--- a/src/database/seeds/local/UserSeeder.php
+++ b/config.demo/src/database/seeds/UserSeeder.php
@@ -1,250 +1,250 @@
 <?php
 
-namespace Database\Seeds\Local;
+namespace Database\Seeds;
 
 use App\Auth\SecondFactor;
 use App\Domain;
 use App\Entitlement;
 use App\User;
 use App\Sku;
 use Carbon\Carbon;
 use Illuminate\Database\Seeder;
 use App\Wallet;
 
 class UserSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $domain = Domain::create(
             [
                 'namespace' => 'kolab.org',
                 'status' => Domain::STATUS_NEW
                     + Domain::STATUS_ACTIVE
                     + Domain::STATUS_CONFIRMED
                     + Domain::STATUS_VERIFIED,
                 'type' => Domain::TYPE_EXTERNAL
             ]
         );
 
         $john = User::create(
             [
                 'email' => 'john@kolab.org',
                 'password' => \App\Utils::generatePassphrase()
             ]
         );
 
         $john->setSettings(
             [
                 'first_name' => 'John',
                 'last_name' => 'Doe',
                 'currency' => 'USD',
                 'country' => 'US',
                 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005",
                 'external_email' => 'john.doe.external@gmail.com',
                 'organization' => 'Kolab Developers',
                 'phone' => '+1 509-248-1111',
             ]
         );
 
         $john->setAliases(['john.doe@kolab.org']);
 
         $wallet = $john->wallets->first();
 
         $packageDomain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first();
         $packageKolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first();
         $packageLite = \App\Package::withEnvTenantContext()->where('title', 'lite')->first();
 
         $domain->assignPackage($packageDomain, $john);
         $john->assignPackage($packageKolab);
 
 
         $appDomain = \App\Domain::where(
             [
                 'namespace' => \config('app.domain')
             ]
         )->first();
 
         $fred = User::create(
             [
                 'email' => 'fred@' . \config('app.domain'),
                 'password' => \App\Utils::generatePassphrase()
             ]
         );
 
         $fred->setSettings(
             [
                 'first_name' => 'fred',
                 'last_name' => 'Doe',
                 'currency' => 'USD',
                 'country' => 'US',
                 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005",
                 'external_email' => 'fred.doe.external@gmail.com',
                 'organization' => 'Kolab Developers',
                 'phone' => '+1 509-248-1111',
             ]
         );
 
         $appDomain->assignPackage($packageDomain, $fred);
         $fred->assignPackage($packageKolab);
 
 
         $jack = User::create(
             [
                 'email' => 'jack@kolab.org',
                 'password' => \App\Utils::generatePassphrase()
             ]
         );
 
         $jack->setSettings(
             [
                 'first_name' => 'Jack',
                 'last_name' => 'Daniels',
                 'currency' => 'USD',
                 'country' => 'US'
             ]
         );
 
         $jack->setAliases(['jack.daniels@kolab.org']);
 
         $john->assignPackage($packageKolab, $jack);
 
         foreach ($john->entitlements as $entitlement) {
             $entitlement->created_at = Carbon::now()->subMonthsWithoutOverflow(1);
             $entitlement->updated_at = Carbon::now()->subMonthsWithoutOverflow(1);
             $entitlement->save();
         }
 
         $ned = User::create(
             [
                 'email' => 'ned@kolab.org',
                 'password' => \App\Utils::generatePassphrase()
             ]
         );
 
         $ned->setSettings(
             [
                 'first_name' => 'Edward',
                 'last_name' => 'Flanders',
                 'currency' => 'USD',
                 'country' => 'US',
                 'guam_enabled' => false,
             ]
         );
 
         $john->assignPackage($packageKolab, $ned);
 
         $ned->assignSku(\App\Sku::withEnvTenantContext()->where('title', 'activesync')->first(), 1);
 
         // Ned is a controller on Jack's wallet
         $john->wallets()->first()->addController($ned);
 
         // Ned is also our 2FA test user
         $sku2fa = Sku::withEnvTenantContext()->where('title', '2fa')->first();
 
         $ned->assignSku($sku2fa);
 
         SecondFactor::seed('ned@kolab.org');
 
         $joe = User::create(
             [
                 'email' => 'joe@kolab.org',
                 'password' => \App\Utils::generatePassphrase()
             ]
         );
 
         $john->assignPackage($packageLite, $joe);
 
         //$john->assignSku(Sku::firstOrCreate(['title' => 'beta']));
         //$john->assignSku(Sku::firstOrCreate(['title' => 'meet']));
 
         $joe->setAliases(['joe.monster@kolab.org']);
 
         // This only exists so the user create job doesn't fail because the domain is not found
         Domain::create(
             [
                 'namespace' => 'jeroen.jeroen',
                 'status' => Domain::STATUS_NEW
                     + Domain::STATUS_ACTIVE
                     + Domain::STATUS_CONFIRMED
                     + Domain::STATUS_VERIFIED,
                 'type' => Domain::TYPE_EXTERNAL
             ]
         );
 
         $jeroen = User::create(
             [
                 'email' => 'jeroen@jeroen.jeroen',
                 'password' => \App\Utils::generatePassphrase()
             ]
         );
 
         $jeroen->role = 'admin';
         $jeroen->save();
 
         $reseller = User::create(
             [
                 'email' => 'reseller@' . \config('app.domain'),
                 'password' => \App\Utils::generatePassphrase()
             ]
         );
 
         $reseller->role = 'reseller';
         $reseller->save();
 
         $reseller->assignPackage($packageKolab);
 
         // for tenants that are not the configured tenant id
         $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get();
 
         foreach ($tenants as $tenant) {
             $domain = Domain::where('tenant_id', $tenant->id)->first();
 
             $packageKolab = \App\Package::where(
                 [
                     'title' => 'kolab',
                     'tenant_id' => $tenant->id
                 ]
             )->first();
 
             if ($domain) {
                 $reseller = User::create(
                     [
                         'email' => 'reseller@' . $domain->namespace,
                         'password' => \App\Utils::generatePassphrase()
                     ]
                 );
 
                 $reseller->role = 'reseller';
                 $reseller->tenant_id = $tenant->id;
                 $reseller->save();
 
                 $reseller->assignPackage($packageKolab);
 
                 $user = User::create(
                     [
                         'email' => 'user@' . $domain->namespace,
                         'password' => \App\Utils::generatePassphrase()
                     ]
                 );
 
                 $user->tenant_id = $tenant->id;
                 $user->save();
 
                 $user->assignPackage($packageKolab);
             }
         }
 
         // Create imap admin service account
         User::create(
             [
                 'email' => \config('imap.admin_login'),
                 'password' => \config('imap.admin_password')
             ]
         );
     }
 }
diff --git a/config.dev/docker-compose.override.yml b/config.dev/docker-compose.override.yml
new file mode 100644
index 00000000..319c9b6f
--- /dev/null
+++ b/config.dev/docker-compose.override.yml
@@ -0,0 +1,55 @@
+version: '3'
+services:
+  kolab:
+    ports:
+      - "389:389"
+      - "8880:8880"
+      - "8443:8443"
+      - "10143:10143"
+      - "10587:10587"
+      - "11143:11143"
+      - "11993:11993"
+      - "12143:12143"
+  mariadb:
+    ports:
+      - "3306:3306"
+  redis:
+    ports:
+      - "6379:6379"
+  haproxy:
+    depends_on:
+      proxy:
+        condition: service_healthy
+  proxy:
+    depends_on:
+      kolab:
+        condition: service_healthy
+      webapp:
+        condition: service_healthy
+    build:
+      context: ./docker/proxy/
+      args:
+        APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err}
+        SSL_CERTIFICATE: ${PROXY_SSL_CERTIFICATE:?err}
+        SSL_CERTIFICATE_KEY: ${PROXY_SSL_CERTIFICATE_KEY:?err}
+    healthcheck:
+      interval: 10s
+      test: "kill -0 $$(cat /run/nginx.pid)"
+      timeout: 5s
+      retries: 30
+    container_name: kolab-proxy
+    hostname: proxy
+    image: kolab-proxy
+    extra_hosts:
+      - "meet:${MEET_LISTENING_HOST}"
+      - "webapp:127.0.0.1"
+    network_mode: host
+    tmpfs:
+      - /run
+      - /tmp
+      - /var/run
+      - /var/tmp
+    tty: true
+    volumes:
+      - ./docker/certs/:/etc/certs/:ro
+      - /etc/letsencrypt/:/etc/letsencrypt/:ro
diff --git a/src/.env.example b/config.dev/src/.env
similarity index 77%
copy from src/.env.example
copy to config.dev/src/.env
index b093a100..dac7465e 100644
--- a/src/.env.example
+++ b/config.dev/src/.env
@@ -1,185 +1,200 @@
 APP_NAME=Kolab
 APP_ENV=local
-APP_KEY=
+APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E=
 APP_DEBUG=true
 APP_URL=https://kolab.local
-#APP_PASSPHRASE=
+APP_PASSPHRASE=simple123
 APP_PUBLIC_URL=https://kolab.local
 APP_DOMAIN=kolab.local
 APP_WEBSITE_DOMAIN=kolab.local
 APP_THEME=default
 APP_TENANT_ID=5
 APP_LOCALE=en
 APP_LOCALES=
 
 APP_WITH_ADMIN=1
 APP_WITH_RESELLER=1
 APP_WITH_SERVICES=1
 APP_WITH_FILES=1
 
 APP_LDAP=1
 
 APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';"
 APP_HEADER_XFO=sameorigin
 
 SIGNUP_LIMIT_EMAIL=0
 SIGNUP_LIMIT_IP=0
 
 ASSET_URL=https://kolab.local
 
 WEBMAIL_URL=/roundcubemail/
 SUPPORT_URL=/support
 SUPPORT_EMAIL=
 
 LOG_CHANNEL=stack
 LOG_SLOW_REQUESTS=5
 LOG_DEPRECATIONS_CHANNEL=null
 LOG_LEVEL=debug
 
 DB_CONNECTION=mysql
 DB_DATABASE=kolabdev
-DB_HOST=mariadb
+DB_HOST=127.0.0.1
 DB_PASSWORD=kolab
 DB_PORT=3306
 DB_USERNAME=kolabdev
 
 BROADCAST_DRIVER=redis
 CACHE_DRIVER=redis
 
 QUEUE_CONNECTION=redis
 
 SESSION_DRIVER=file
 SESSION_LIFETIME=120
 
 OPENEXCHANGERATES_API_KEY="from openexchangerates.org"
 
-MFA_DSN=mysql://roundcube:Welcome2KolabSystems@mariadb/roundcube
+MFA_DSN=mysql://root:Welcome2KolabSystems@127.0.0.1/roundcube
 MFA_TOTP_DIGITS=6
 MFA_TOTP_INTERVAL=30
 MFA_TOTP_DIGEST=sha1
 
-IMAP_URI=ssl://kolab:11993
-IMAP_HOST=172.18.0.5
+IMAP_URI=ssl://127.0.0.1:11993
+IMAP_HOST=127.0.0.1
 IMAP_ADMIN_LOGIN=cyrus-admin
 IMAP_ADMIN_PASSWORD=Welcome2KolabSystems
 IMAP_VERIFY_HOST=false
 IMAP_VERIFY_PEER=false
 
 LDAP_BASE_DN="dc=mgmt,dc=com"
 LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com"
-LDAP_HOSTS=kolab
+LDAP_HOSTS=127.0.0.1
 LDAP_PORT=389
 LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com"
 LDAP_SERVICE_BIND_PW="Welcome2KolabSystems"
 LDAP_USE_SSL=false
 LDAP_USE_TLS=false
 
 # Administrative
 LDAP_ADMIN_BIND_DN="cn=Directory Manager"
 LDAP_ADMIN_BIND_PW="Welcome2KolabSystems"
 LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com"
 
 # Hosted (public registration)
 LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com"
 LDAP_HOSTED_BIND_PW="Welcome2KolabSystems"
 LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com"
 
-COTURN_PUBLIC_IP='172.18.0.1'
+COTURN_PUBLIC_IP='127.0.0.1'
 COTURN_STATIC_SECRET="Welcome2KolabSystems"
 
 MEET_WEBHOOK_TOKEN=Welcome2KolabSystems
 MEET_SERVER_TOKEN=Welcome2KolabSystems
-MEET_SERVER_URLS=https://kolab.local/meetmedia/api/
-MEET_SERVER_VERIFY_TLS=false
 
-MEET_WEBRTC_LISTEN_IP='172.18.0.1'
+MEET_TURN_SERVER='turn:127.0.0.1:3478'
+MEET_WEBRTC_LISTEN_IP='127.0.0.1'
+MEET_LISTENING_HOST=127.0.0.1
 MEET_PUBLIC_DOMAIN=kolab.local
-MEET_TURN_SERVER='turn:172.18.0.1:3478'
-MEET_LISTENING_HOST=172.18.0.1
+MEET_SERVER_URLS=https://127.0.0.1/meetmedia/api/
+MEET_SERVER_VERIFY_TLS=false
 
 
 PGP_ENABLE=true
 PGP_BINARY=/usr/bin/gpg
 PGP_AGENT=/usr/bin/gpg-agent
 PGP_GPGCONF=/usr/bin/gpgconf
 PGP_LENGTH=
 
 # Set these to IP addresses you serve WOAT with.
 # Have the domain owner point _woat.<hosted-domain> NS RRs refer to ns0{1,2}.<provider-domain>
 WOAT_NS1=ns01.domain.tld
 WOAT_NS2=ns02.domain.tld
 
-REDIS_HOST=redis
+REDIS_HOST=127.0.0.1
 REDIS_PASSWORD=null
 REDIS_PORT=6379
 
 OCTANE_HTTP_HOST=127.0.0.1
 SWOOLE_PACKAGE_MAX_LENGTH=10485760
 
 PAYMENT_PROVIDER=
 MOLLIE_KEY=
 STRIPE_KEY=
 STRIPE_PUBLIC_KEY=
 STRIPE_WEBHOOK_SECRET=
 
 MAIL_DRIVER=log
 MAIL_MAILER=smtp
 MAIL_HOST=smtp.mailtrap.io
 MAIL_PORT=2525
 MAIL_USERNAME=null
 MAIL_PASSWORD=null
 MAIL_ENCRYPTION=null
 MAIL_FROM_ADDRESS="noreply@example.com"
 MAIL_FROM_NAME="Example.com"
 MAIL_REPLYTO_ADDRESS="replyto@example.com"
 MAIL_REPLYTO_NAME=null
 
 DNS_TTL=3600
 DNS_SPF="v=spf1 mx -all"
 DNS_STATIC="%s.    MX  10 ext-mx01.mykolab.com."
 DNS_COPY_FROM=null
 
 AWS_ACCESS_KEY_ID=
 AWS_SECRET_ACCESS_KEY=
 AWS_DEFAULT_REGION=us-east-1
 AWS_BUCKET=
 AWS_USE_PATH_STYLE_ENDPOINT=false
 
 PUSHER_APP_ID=
 PUSHER_APP_KEY=
 PUSHER_APP_SECRET=
 PUSHER_APP_CLUSTER=mt1
 
 MIX_ASSET_PATH='/'
 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
 MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
 
 # Generate with ./artisan passport:client --password
 #PASSPORT_PROXY_OAUTH_CLIENT_ID=
 #PASSPORT_PROXY_OAUTH_CLIENT_SECRET=
 
+# Generate with ./artisan passport:client --password
+#PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=
+#PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=
+
 PASSPORT_PRIVATE_KEY=
 PASSPORT_PUBLIC_KEY=
 
 PASSWORD_POLICY=
 
 COMPANY_NAME=
 COMPANY_ADDRESS=
 COMPANY_DETAILS=
 COMPANY_EMAIL=
 COMPANY_LOGO=
 COMPANY_FOOTER=
 
 VAT_COUNTRIES=CH,LI
 VAT_RATE=7.7
 
 KB_ACCOUNT_DELETE=
 KB_ACCOUNT_SUSPENDED=
 KB_PAYMENT_SYSTEM=
 
 KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert
 KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem
 KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key
 
 PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert
 PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key
+
+OX_API_KEY=
+FIREBASE_API_KEY=
+
+#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
+PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee
+PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf
+
+#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up)
+PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb
+PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz
diff --git a/config.dev/src/database b/config.dev/src/database
new file mode 120000
index 00000000..28204f1b
--- /dev/null
+++ b/config.dev/src/database
@@ -0,0 +1 @@
+../../config.demo/src/database
\ No newline at end of file
diff --git a/config.prod/docker-compose.override.yml b/config.prod/docker-compose.override.yml
new file mode 120000
index 00000000..16676b85
--- /dev/null
+++ b/config.prod/docker-compose.override.yml
@@ -0,0 +1 @@
+../config.demo/docker-compose.override.yml
\ No newline at end of file
diff --git a/src/.env.example b/config.prod/src/.env
similarity index 68%
rename from src/.env.example
rename to config.prod/src/.env
index b093a100..c3c6a6b1 100644
--- a/src/.env.example
+++ b/config.prod/src/.env
@@ -1,185 +1,157 @@
 APP_NAME=Kolab
 APP_ENV=local
 APP_KEY=
 APP_DEBUG=true
-APP_URL=https://kolab.local
-#APP_PASSPHRASE=
-APP_PUBLIC_URL=https://kolab.local
-APP_DOMAIN=kolab.local
-APP_WEBSITE_DOMAIN=kolab.local
+APP_URL=https://{{ host }}
+APP_PUBLIC_URL=https://{{ host }}
+APP_DOMAIN={{ host }}
+APP_WEBSITE_DOMAIN={{ host }}
 APP_THEME=default
 APP_TENANT_ID=5
 APP_LOCALE=en
 APP_LOCALES=
 
 APP_WITH_ADMIN=1
 APP_WITH_RESELLER=1
 APP_WITH_SERVICES=1
 APP_WITH_FILES=1
 
 APP_LDAP=1
+APP_IMAP=0
 
 APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';"
 APP_HEADER_XFO=sameorigin
 
 SIGNUP_LIMIT_EMAIL=0
 SIGNUP_LIMIT_IP=0
 
-ASSET_URL=https://kolab.local
+ASSET_URL=https://{{ host }}
 
 WEBMAIL_URL=/roundcubemail/
 SUPPORT_URL=/support
 SUPPORT_EMAIL=
 
 LOG_CHANNEL=stack
 LOG_SLOW_REQUESTS=5
 LOG_DEPRECATIONS_CHANNEL=null
 LOG_LEVEL=debug
 
 DB_CONNECTION=mysql
 DB_DATABASE=kolabdev
 DB_HOST=mariadb
-DB_PASSWORD=kolab
+DB_PASSWORD={{ admin_password }}
 DB_PORT=3306
 DB_USERNAME=kolabdev
 
 BROADCAST_DRIVER=redis
 CACHE_DRIVER=redis
 
 QUEUE_CONNECTION=redis
 
 SESSION_DRIVER=file
 SESSION_LIFETIME=120
 
 OPENEXCHANGERATES_API_KEY="from openexchangerates.org"
 
-MFA_DSN=mysql://roundcube:Welcome2KolabSystems@mariadb/roundcube
+MFA_DSN=mysql://roundcube:{{ admin_password }}@mariadb/roundcube
 MFA_TOTP_DIGITS=6
 MFA_TOTP_INTERVAL=30
 MFA_TOTP_DIGEST=sha1
 
 IMAP_URI=ssl://kolab:11993
 IMAP_HOST=172.18.0.5
 IMAP_ADMIN_LOGIN=cyrus-admin
-IMAP_ADMIN_PASSWORD=Welcome2KolabSystems
+IMAP_ADMIN_PASSWORD={{ admin_password }}
 IMAP_VERIFY_HOST=false
 IMAP_VERIFY_PEER=false
 
 LDAP_BASE_DN="dc=mgmt,dc=com"
 LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com"
 LDAP_HOSTS=kolab
 LDAP_PORT=389
 LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com"
-LDAP_SERVICE_BIND_PW="Welcome2KolabSystems"
+LDAP_SERVICE_BIND_PW="{{ admin_password }}"
 LDAP_USE_SSL=false
 LDAP_USE_TLS=false
 
 # Administrative
 LDAP_ADMIN_BIND_DN="cn=Directory Manager"
-LDAP_ADMIN_BIND_PW="Welcome2KolabSystems"
+LDAP_ADMIN_BIND_PW="{{ admin_password }}"
 LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com"
 
 # Hosted (public registration)
 LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com"
-LDAP_HOSTED_BIND_PW="Welcome2KolabSystems"
+LDAP_HOSTED_BIND_PW="{{ admin_password }}"
 LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com"
 
-COTURN_PUBLIC_IP='172.18.0.1'
-COTURN_STATIC_SECRET="Welcome2KolabSystems"
+COTURN_PUBLIC_IP='{{ public_ip }}'
 
-MEET_WEBHOOK_TOKEN=Welcome2KolabSystems
-MEET_SERVER_TOKEN=Welcome2KolabSystems
-MEET_SERVER_URLS=https://kolab.local/meetmedia/api/
+MEET_SERVER_URLS=https://{{ host }}/meetmedia/api/
 MEET_SERVER_VERIFY_TLS=false
 
 MEET_WEBRTC_LISTEN_IP='172.18.0.1'
-MEET_PUBLIC_DOMAIN=kolab.local
+MEET_PUBLIC_DOMAIN={{ host }}
 MEET_TURN_SERVER='turn:172.18.0.1:3478'
 MEET_LISTENING_HOST=172.18.0.1
 
 
 PGP_ENABLE=true
 PGP_BINARY=/usr/bin/gpg
 PGP_AGENT=/usr/bin/gpg-agent
 PGP_GPGCONF=/usr/bin/gpgconf
 PGP_LENGTH=
 
-# Set these to IP addresses you serve WOAT with.
-# Have the domain owner point _woat.<hosted-domain> NS RRs refer to ns0{1,2}.<provider-domain>
-WOAT_NS1=ns01.domain.tld
-WOAT_NS2=ns02.domain.tld
-
 REDIS_HOST=redis
 REDIS_PASSWORD=null
 REDIS_PORT=6379
 
-OCTANE_HTTP_HOST=127.0.0.1
+OCTANE_HTTP_HOST={{ host }}
 SWOOLE_PACKAGE_MAX_LENGTH=10485760
 
-PAYMENT_PROVIDER=
-MOLLIE_KEY=
-STRIPE_KEY=
-STRIPE_PUBLIC_KEY=
-STRIPE_WEBHOOK_SECRET=
-
 MAIL_DRIVER=log
 MAIL_MAILER=smtp
 MAIL_HOST=smtp.mailtrap.io
 MAIL_PORT=2525
 MAIL_USERNAME=null
 MAIL_PASSWORD=null
 MAIL_ENCRYPTION=null
 MAIL_FROM_ADDRESS="noreply@example.com"
 MAIL_FROM_NAME="Example.com"
 MAIL_REPLYTO_ADDRESS="replyto@example.com"
 MAIL_REPLYTO_NAME=null
 
 DNS_TTL=3600
 DNS_SPF="v=spf1 mx -all"
 DNS_STATIC="%s.    MX  10 ext-mx01.mykolab.com."
 DNS_COPY_FROM=null
 
-AWS_ACCESS_KEY_ID=
-AWS_SECRET_ACCESS_KEY=
-AWS_DEFAULT_REGION=us-east-1
-AWS_BUCKET=
-AWS_USE_PATH_STYLE_ENDPOINT=false
-
-PUSHER_APP_ID=
-PUSHER_APP_KEY=
-PUSHER_APP_SECRET=
-PUSHER_APP_CLUSTER=mt1
-
 MIX_ASSET_PATH='/'
-MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
-MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
-
-# Generate with ./artisan passport:client --password
-#PASSPORT_PROXY_OAUTH_CLIENT_ID=
-#PASSPORT_PROXY_OAUTH_CLIENT_SECRET=
 
 PASSPORT_PRIVATE_KEY=
 PASSPORT_PUBLIC_KEY=
 
 PASSWORD_POLICY=
 
 COMPANY_NAME=
 COMPANY_ADDRESS=
 COMPANY_DETAILS=
 COMPANY_EMAIL=
 COMPANY_LOGO=
 COMPANY_FOOTER=
 
 VAT_COUNTRIES=CH,LI
 VAT_RATE=7.7
 
 KB_ACCOUNT_DELETE=
 KB_ACCOUNT_SUSPENDED=
 KB_PAYMENT_SYSTEM=
 
 KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert
 KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem
 KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key
 
 PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert
 PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key
+
+OPENEXCHANGERATES_API_KEY={{ openexchangerates_api_key }}
+FIREBASE_API_KEY={{ firebase_api_key }}
diff --git a/config.prod/src/database/migrations b/config.prod/src/database/migrations
new file mode 120000
index 00000000..dae12a41
--- /dev/null
+++ b/config.prod/src/database/migrations
@@ -0,0 +1 @@
+../../../config.demo/src/database/migrations
\ No newline at end of file
diff --git a/config.prod/src/database/seeds/AdminSeeder.php b/config.prod/src/database/seeds/AdminSeeder.php
new file mode 100644
index 00000000..35e0758b
--- /dev/null
+++ b/config.prod/src/database/seeds/AdminSeeder.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Database\Seeds;
+
+use App\Sku;
+use App\Package;
+use App\Domain;
+use App\User;
+use Laravel\Passport\Passport;
+use Illuminate\Database\Seeder;
+use Illuminate\Encryption\Encrypter;
+
+class AdminSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     *
+     * Create a default user with dependencies
+     *
+     * @return void
+     */
+    public function run()
+    {
+        //Create required packages
+        $skuDomain = Sku::where(['title' => 'domain-hosting', 'tenant_id' => \config('app.tenant_id')])->first();
+        $skuGroupware = Sku::where(['title' => 'groupware', 'tenant_id' => \config('app.tenant_id')])->first();
+        $skuMailbox = Sku::where(['title' => 'mailbox', 'tenant_id' => \config('app.tenant_id')])->first();
+        $skuStorage = Sku::where(['title' => 'storage', 'tenant_id' => \config('app.tenant_id')])->first();
+
+        $packageKolab = Package::create(
+            [
+                'title' => 'kolab',
+                'name' => 'Groupware Account',
+                'description' => 'A fully functional groupware account.',
+                'discount_rate' => 0,
+            ]
+        );
+        $packageKolab->skus()->saveMany([
+            $skuMailbox,
+            $skuGroupware,
+            $skuStorage
+        ]);
+
+
+        $packageDomain = Package::create(
+            [
+                'title' => 'domain-hosting',
+                'name' => 'Domain Hosting',
+                'description' => 'Use your own, existing domain.',
+                'discount_rate' => 0,
+            ]
+        );
+        $packageDomain->skus()->saveMany([
+            $skuDomain
+        ]);
+
+
+
+        //Create primary domain
+        $appDomain = Domain::create(
+            [
+                'namespace' => \config('app.domain'),
+                'status' => DOMAIN::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
+                'type' => Domain::TYPE_PUBLIC,
+            ]
+        );
+
+        //Create admin user
+        $admin = User::create(
+            [
+                'email' => 'admin@' . \config('app.domain'),
+                'password' => \App\Utils::generatePassphrase()
+            ]
+        );
+
+        $admin->setSettings(
+            [
+                'first_name' => 'Admin',
+            ]
+        );
+
+        $appDomain->assignPackage($packageDomain, $admin);
+        $admin->assignPackage($packageKolab);
+    }
+}
+
diff --git a/config.prod/src/database/seeds/AppKeySeeder.php b/config.prod/src/database/seeds/AppKeySeeder.php
new file mode 100644
index 00000000..4554bf5d
--- /dev/null
+++ b/config.prod/src/database/seeds/AppKeySeeder.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Database\Seeds;
+
+use Laravel\Passport\Passport;
+use Illuminate\Database\Seeder;
+use Illuminate\Encryption\Encrypter;
+
+class AppKeySeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     *
+     * This emulates './artisan key:generate'
+     *
+     * @return void
+     */
+    public function run()
+    {
+        $key = $this->generateRandomKey();
+        $this->writeNewEnvironmentFileWith($key);
+    }
+
+    /**
+     * Generate a random key for the application.
+     *
+     * @return string
+     */
+    protected function generateRandomKey()
+    {
+        return 'base64:' . base64_encode(
+            Encrypter::generateKey(\config('app.cipher'))
+        );
+    }
+
+    /**
+     * Write a new environment file with the given key.
+     *
+     * @param  string  $key
+     * @return void
+     */
+    protected function writeNewEnvironmentFileWith($key)
+    {
+        file_put_contents(\app()->environmentFilePath(), preg_replace(
+            $this->keyReplacementPattern(),
+            'APP_KEY=' . $key,
+            file_get_contents(\app()->environmentFilePath())
+        ));
+    }
+
+    /**
+     * Get a regex pattern that will match env APP_KEY with any random key.
+     *
+     * @return string
+     */
+    protected function keyReplacementPattern()
+    {
+        $escaped = preg_quote('=' . \config('app.key'), '/');
+        return "/^APP_KEY{$escaped}/m";
+    }
+}
+
diff --git a/config.prod/src/database/seeds/DatabaseSeeder.php b/config.prod/src/database/seeds/DatabaseSeeder.php
new file mode 100644
index 00000000..b6b4d24a
--- /dev/null
+++ b/config.prod/src/database/seeds/DatabaseSeeder.php
@@ -0,0 +1,24 @@
+<?php
+
+use Illuminate\Database\Seeder;
+use Database\Seeds;
+
+// phpcs:ignore
+class DatabaseSeeder extends Seeder
+{
+    /**
+     * Seed the application's database.
+     *
+     * @return void
+     */
+    public function run()
+    {
+        $this->call([
+            Seeds\AppKeySeeder::class,
+            Seeds\PassportSeeder::class,
+            Seeds\PowerDNSSeeder::class,
+            Seeds\SkuSeeder::class,
+            Seeds\AdminSeeder::class,
+        ]);
+    }
+}
diff --git a/config.prod/src/database/seeds/PassportSeeder.php b/config.prod/src/database/seeds/PassportSeeder.php
new file mode 100644
index 00000000..4bc8aa1e
--- /dev/null
+++ b/config.prod/src/database/seeds/PassportSeeder.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Database\Seeds;
+
+use Laravel\Passport\Passport;
+use Illuminate\Database\Seeder;
+use Illuminate\Encryption\Encrypter;
+use phpseclib3\Crypt\RSA;
+
+class PassportSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     *
+     * This emulates:
+     * './artisan passport:keys --force'
+     * './artisan passport:client --password --name="Kolab Password Grant Client" --provider=users'
+     *
+     * @return void
+     */
+    public function run()
+    {
+        //First initialize the passport keys
+        [$publicKey, $privateKey] = [
+            Passport::keyPath('oauth-public.key'),
+            Passport::keyPath('oauth-private.key'),
+        ];
+        $key = RSA::createKey(4096);
+        file_put_contents($publicKey, (string) $key->getPublicKey());
+        file_put_contents($privateKey, (string) $key);
+
+        $this->writeNewEnvironmentFileWith('PASSPORT_PRIVATE_KEY', 'passport.private_key', $key);
+        $this->writeNewEnvironmentFileWith('PASSPORT_PUBLIC_KEY', 'passport.public_key', (string) $key->getPublicKey());
+
+        //Create a password grant client for the webapp
+        $secret = $this->generateRandomKey();
+
+        $client = Passport::client()->forceFill([
+            'user_id' => null,
+            'name' => "Kolab Password Grant Client",
+            'secret' => $secret,
+            'provider' => 'users',
+            'redirect' => 'https://' . \config('app.website_domain'),
+            'personal_access_client' => 0,
+            'password_client' => 1,
+            'revoked' => false,
+        ]);
+        $client->save();
+
+        $this->writeNewEnvironmentFileWith('PASSPORT_PROXY_OAUTH_CLIENT_ID', 'auth.proxy.client_id', $client->id);
+        $this->writeNewEnvironmentFileWith('PASSPORT_PROXY_OAUTH_CLIENT_SECRET', 'auth.proxy.client_secret', $secret);
+    }
+
+    /**
+     * Generate a random key for the application.
+     *
+     * @return string
+     */
+    protected function generateRandomKey()
+    {
+        return base64_encode(
+            Encrypter::generateKey(\config('app.cipher'))
+        );
+    }
+
+    /**
+     * Write a new environment file with the given key.
+     *
+     * @param  string  $key
+     * @param  string  $configKey
+     * @param  string  $value
+     * @return void
+     */
+    protected function writeNewEnvironmentFileWith($key, $configKey, $value)
+    {
+        $path = \app()->environmentFilePath();
+        $count = 0;
+        $line = "{$key}=\"{$value}\"";
+        $result = preg_replace(
+            $this->keyReplacementPattern($key, \config($configKey)),
+            $line,
+            file_get_contents($path),
+            -1,
+            $count
+        );
+        //Append if it doesn't exist
+        if ($count == 0) {
+            $result = $result . "\n$line";
+        }
+        file_put_contents($path, $result);
+    }
+
+    /**
+     * Get a regex pattern that will match env APP_KEY with any random key.
+     *
+     * @return string
+     */
+    protected function keyReplacementPattern($key, $value)
+    {
+        $escaped = preg_quote("={$value}", '/');
+        return "/^{$key}{$escaped}/m";
+    }
+}
diff --git a/config.prod/src/database/seeds/PowerDNSSeeder.php b/config.prod/src/database/seeds/PowerDNSSeeder.php
new file mode 100644
index 00000000..6cd5f338
--- /dev/null
+++ b/config.prod/src/database/seeds/PowerDNSSeeder.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Database\Seeds;
+
+use App\Domain;
+use Illuminate\Database\Seeder;
+
+class PowerDNSSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     *
+     * @return void
+     */
+    public function run()
+    {
+        $domain = \App\PowerDNS\Domain::create(
+            [
+                'name' => '_woat.' . \config('app.domain')
+            ]
+        );
+    }
+}
diff --git a/src/database/seeds/production/SkuSeeder.php b/config.prod/src/database/seeds/SkuSeeder.php
similarity index 93%
rename from src/database/seeds/production/SkuSeeder.php
rename to config.prod/src/database/seeds/SkuSeeder.php
index c18b77aa..cceae31d 100644
--- a/src/database/seeds/production/SkuSeeder.php
+++ b/config.prod/src/database/seeds/SkuSeeder.php
@@ -1,172 +1,172 @@
 <?php
 
-namespace Database\Seeds\Production;
+namespace Database\Seeds;
 
 use App\Sku;
 use Illuminate\Database\Seeder;
 
 class SkuSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
         $skus = [
             [
                 'title' => 'mailbox',
                 'name' => 'User Mailbox',
                 'description' => 'Just a mailbox',
-                'cost' => 444,
+                'cost' => 500,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Mailbox',
                 'active' => true,
             ],
             [
                 'title' => 'domain',
                 'name' => 'Hosted Domain',
                 'description' => 'Somewhere to place a mailbox',
                 'cost' => 100,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Domain',
                 'active' => false,
             ],
             [
                 'title' => 'domain-registration',
                 'name' => 'Domain Registration',
                 'description' => 'Register a domain with us',
                 'cost' => 101,
                 'period' => 'yearly',
                 'handler_class' => 'App\Handlers\DomainRegistration',
                 'active' => false,
             ],
             [
                 'title' => 'domain-hosting',
                 'name' => 'External Domain',
                 'description' => 'Host a domain that is externally registered',
                 'cost' => 100,
                 'units_free' => 1,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\DomainHosting',
                 'active' => true,
             ],
             [
                 'title' => 'domain-relay',
                 'name' => 'Domain Relay',
                 'description' => 'A domain you host at home, for which we relay email',
                 'cost' => 103,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\DomainRelay',
                 'active' => false,
             ],
             [
                 'title' => 'storage',
                 'name' => 'Storage Quota',
                 'description' => 'Some wiggle room',
-                'cost' => 50,
-                'units_free' => 2,
+                'cost' => 25,
+                'units_free' => 5,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Storage',
                 'active' => true,
             ],
             [
                 'title' => 'groupware',
                 'name' => 'Groupware Features',
                 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.',
-                'cost' => 555,
+                'cost' => 490,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Groupware',
                 'active' => true,
             ],
             [
                 'title' => 'resource',
                 'name' => 'Resource',
                 'description' => 'Reservation taker',
-                'cost' => 0,
+                'cost' => 101,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Resource',
                 'active' => true,
             ],
             [
                 'title' => 'shared-folder',
                 'name' => 'Shared Folder',
                 'description' => 'A shared folder',
                 'cost' => 89,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\SharedFolder',
-                'active' => false,
+                'active' => true,
             ],
             [
                 'title' => '2fa',
                 'name' => '2-Factor Authentication',
                 'description' => 'Two factor authentication for webmail and administration panel',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Auth2F',
                 'active' => true,
             ],
             [
                 'title' => 'activesync',
                 'name' => 'Activesync',
                 'description' => 'Mobile synchronization',
-                'cost' => 100,
+                'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Activesync',
                 'active' => true,
             ],
             [
                 'title' => 'beta',
                 'name' => 'Private Beta (invitation only)',
-                'description' => 'Access to the private beta program subscriptions',
+                'description' => 'Access to the private beta program features',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Beta',
                 'active' => false,
             ],
             [
                 'title' => 'group',
                 'name' => 'Distribution list',
                 'description' => 'Mail distribution list',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Group',
                 'active' => true,
             ],
             [
                 'title' => 'group-room',
                 'name' => 'Group conference room',
                 'description' => 'Shareable audio & video conference room',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\GroupRoom',
                 'active' => true,
             ],
             [
-               'title' => 'room',
+                'title' => 'room',
                 'name' => 'Standard conference room',
                 'description' => 'Audio & video conference room',
                 'cost' => 0,
                 'units_free' => 0,
                 'period' => 'monthly',
                 'handler_class' => 'App\Handlers\Room',
                 'active' => true,
             ],
         ];
 
         foreach ($skus as $sku) {
             // Check existence because migration might have added this already
-            if (!Sku::where('title', $sku['title'])->first()) {
+            if (!Sku::where('title', $sku['title'])->where('tenant_id', \config('app.tenant_id'))->first()) {
                 Sku::create($sku);
             }
         }
     }
 }
diff --git a/docker-compose.local.yml b/docker-compose.local.yml
deleted file mode 100644
index 528a53a4..00000000
--- a/docker-compose.local.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-version: '3'
-services:
-  kolab:
-    ports:
-      - "389:389"
-      - "8880:8880"
-      - "8443:8443"
-      - "10143:10143"
-      - "10587:10587"
-      - "11143:11143"
-      - "11993:11993"
-      - "12143:12143"
-  mariadb:
-    ports:
-      - "3306:3306"
-  redis:
-    ports:
-      - "6379:6379"
-  proxy:
-    extra_hosts:
-      - "webapp:127.0.0.1"
diff --git a/docker-compose.yml b/docker-compose.yml
index f1151661..07dbe8d1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,277 +1,240 @@
 version: '3'
 services:
   coturn:
     build:
       context: ./docker/coturn/
     container_name: kolab-coturn
     healthcheck:
       interval: 10s
       test: "kill -0 $$(cat /tmp/turnserver.pid)"
       timeout: 5s
       retries: 30
     environment:
       - TURN_PUBLIC_IP=${COTURN_PUBLIC_IP}
       - TURN_LISTEN_PORT=3478
       - TURN_STATIC_SECRET=${COTURN_STATIC_SECRET}
     hostname: sturn.mgmt.com
     image: kolab-coturn
     network_mode: host
     restart: on-failure
     tty: true
   kolab:
     build:
       context: ./docker/kolab/
     container_name: kolab
     privileged: true
     depends_on:
       mariadb:
         condition: service_healthy
       pdns:
         condition: service_healthy
     extra_hosts:
       - "kolab.mgmt.com:127.0.0.1"
       - "services.${APP_DOMAIN}:172.18.0.4"
     environment:
       - APP_DOMAIN=${APP_DOMAIN}
       - LDAP_HOST=127.0.0.1
-      - LDAP_ADMIN_BIND_DN="cn=Directory Manager"
-      - LDAP_ADMIN_BIND_PW=Welcome2KolabSystems
+      - LDAP_ADMIN_BIND_DN=${LDAP_ADMIN_BIND_DN}
+      - LDAP_ADMIN_BIND_PW=${LDAP_ADMIN_BIND_PW}
+      - LDAP_SERVICE_BIND_PW=${LDAP_SERVICE_BIND_PW}
+      - LDAP_HOSTED_BIND_PW=${LDAP_HOSTED_BIND_PW}
       - DB_HOST=mariadb
-      - DB_ROOT_PASSWORD=Welcome2KolabSystems
+      - DB_ROOT_PASSWORD=${DB_PASSWORD}
       - DB_HKCCP_DATABASE=${DB_DATABASE}
       - DB_HKCCP_USERNAME=${DB_USERNAME}
       - DB_HKCCP_PASSWORD=${DB_PASSWORD}
       - DB_KOLAB_DATABASE=kolab
       - DB_KOLAB_USERNAME=kolab
-      - DB_KOLAB_PASSWORD=Welcome2KolabSystems
+      - DB_KOLAB_PASSWORD=${DB_PASSWORD}
       - DB_RC_USERNAME=roundcube
-      - DB_RC_PASSWORD=Welcome2KolabSystems
+      - DB_RC_PASSWORD=${DB_PASSWORD}
       - SSL_CERTIFICATE=${KOLAB_SSL_CERTIFICATE:?err}
       - SSL_CERTIFICATE_FULLCHAIN=${KOLAB_SSL_CERTIFICATE_FULLCHAIN:?err}
       - SSL_CERTIFICATE_KEY=${KOLAB_SSL_CERTIFICATE_KEY:?err}
       - IMAP_HOST=127.0.0.1
       - IMAP_PORT=11993
+      - IMAP_ADMIN_LOGIN=${IMAP_ADMIN_LOGIN}
+      - IMAP_ADMIN_PASSWORD=${IMAP_ADMIN_PASSWORD}
       - MAIL_HOST=127.0.0.1
       - MAIL_PORT=10587
     healthcheck:
       interval: 10s
       test: "systemctl is-active kolab-init || exit 1"
       timeout: 5s
       retries: 30
       start_period: 5m
     # This makes docker's dns, resolve via pdns for this container.
     # Please note it does not affect /etc/resolv.conf
     dns: 172.18.0.11
     hostname: kolab.mgmt.com
     image: kolab
     networks:
       kolab:
         ipv4_address: 172.18.0.5
     ports:
       - "12143:12143"
     tmpfs:
       - /run
       - /tmp
       - /var/run
       - /var/tmp
     tty: true
     volumes:
       - ./ext/:/src/:ro
       - /etc/letsencrypt/:/etc/letsencrypt/:ro
       - ./docker/certs/ca.cert:/etc/pki/tls/certs/ca.cert:ro
       - ./docker/certs/ca.cert:/etc/pki/ca-trust/source/anchors/ca.cert:ro
       - ./docker/certs/kolab.hosted.com.cert:${KOLAB_SSL_CERTIFICATE:?err}
       - ./docker/certs/kolab.hosted.com.chain.pem:${KOLAB_SSL_CERTIFICATE_FULLCHAIN:?err}
       - ./docker/certs/kolab.hosted.com.key:${KOLAB_SSL_CERTIFICATE_KEY:?err}
       - ./docker/kolab/utils:/root/utils:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - imap:/imapdata
       - ldap:/ldapdata
   mariadb:
     container_name: kolab-mariadb
     environment:
-      - MARIADB_ROOT_PASSWORD=Welcome2KolabSystems
+      - MARIADB_ROOT_PASSWORD=${DB_PASSWORD}
       - TZ="+02:00"
       - DB_HKCCP_DATABASE=${DB_DATABASE}
       - DB_HKCCP_USERNAME=${DB_USERNAME}
       - DB_HKCCP_PASSWORD=${DB_PASSWORD}
     healthcheck:
       interval: 10s
       test: test -e /var/run/mysqld/mysqld.sock
       timeout: 5s
       retries: 30
     image: mariadb:latest
     networks:
       - kolab
     volumes:
       - ./docker/mariadb/mysql-init/:/docker-entrypoint-initdb.d/
       - mariadb:/var/lib/mysql
   haproxy:
-    depends_on:
-      proxy:
-        condition: service_healthy
     build:
       context: ./docker/haproxy/
     healthcheck:
       interval: 10s
       test: "kill -0 $$(cat /var/run/haproxy.pid)"
       timeout: 5s
       retries: 30
     container_name: kolab-haproxy
     hostname: haproxy.hosted.com
     image: kolab-haproxy
     networks:
       - kolab
     tmpfs:
       - /run
       - /tmp
       - /var/run
       - /var/tmp
     tty: true
     volumes:
       - ./docker/certs/:/etc/certs/:ro
       - /etc/letsencrypt/:/etc/letsencrypt/:ro
   pdns:
     build:
       context: ./docker/pdns/
+      args:
+        DB_HOST: ${DB_HOST:?err}
+        DB_DATABASE: ${DB_DATABASE:?err}
+        DB_USERNAME: ${DB_USERNAME:?err}
+        DB_PASSWORD: ${DB_PASSWORD:?err}
     container_name: kolab-pdns
     hostname: pdns
     depends_on:
       mariadb:
         condition: service_healthy
     healthcheck:
       interval: 10s
       test: "systemctl status pdns || exit 1"
       timeout: 5s
       retries: 30
     image: kolab-pdns
     networks:
       kolab:
         ipv4_address: 172.18.0.11
     tmpfs:
       - /run
       - /tmp
       - /var/run
       - /var/tmp
     tty: true
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
-  proxy:
-    depends_on:
-      kolab:
-        condition: service_healthy
-      webapp:
-        condition: service_healthy
-    build:
-      context: ./docker/proxy/
-      args:
-        APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err}
-        SSL_CERTIFICATE: ${PROXY_SSL_CERTIFICATE:?err}
-        SSL_CERTIFICATE_KEY: ${PROXY_SSL_CERTIFICATE_KEY:?err}
-    healthcheck:
-      interval: 10s
-      test: "kill -0 $$(cat /run/nginx.pid)"
-      timeout: 5s
-      retries: 30
-    container_name: kolab-proxy
-    hostname: proxy
-    image: kolab-proxy
-    extra_hosts:
-      - "meet:${MEET_LISTENING_HOST}"
-    networks:
-      kolab:
-        ipv4_address: 172.18.0.7
-    tmpfs:
-      - /run
-      - /tmp
-      - /var/run
-      - /var/tmp
-    tty: true
-    volumes:
-      - ./docker/certs/:/etc/certs/:ro
-      - /etc/letsencrypt/:/etc/letsencrypt/:ro
-    ports:
-      # - "80:80"
-      - "443:443"
-      - "465:465"
-      - "587:587"
-      - "143:143"
-      - "993:993"
   redis:
     build:
       context: ./docker/redis/
     healthcheck:
       interval: 10s
       test: "redis-cli ping || exit 1"
       timeout: 5s
       retries: 30
     container_name: kolab-redis
     hostname: redis
     image: redis
     networks:
       - kolab
     volumes:
       - ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
-    # ports:
-    #   - "6379:6379"
   webapp:
     build:
       context: ./docker/webapp/
     container_name: kolab-webapp
     image: kolab-webapp
     healthcheck:
       interval: 10s
       test: "/src/kolabsrc/artisan octane:status || exit 1"
       timeout: 5s
       retries: 30
       start_period: 5m
     depends_on:
       kolab:
         condition: service_healthy
       redis:
         condition: service_healthy
     networks:
       kolab:
         ipv4_address: 172.18.0.4
     volumes:
       - ./src:/src/kolabsrc.orig:ro
     ports:
       - "8000:8000"
   meet:
     build:
       context: ./docker/meet/
     healthcheck:
       interval: 10s
       test: "curl --insecure -H 'X-AUTH-TOKEN: ${MEET_SERVER_TOKEN}' --fail https://${MEET_LISTENING_HOST}:12443/meetmedia/api/health || exit 1"
       timeout: 5s
       retries: 30
       start_period: 5m
     environment:
       - WEBRTC_LISTEN_IP=${MEET_WEBRTC_LISTEN_IP:?err}
       - PUBLIC_DOMAIN=${MEET_PUBLIC_DOMAIN:?err}
       - LISTENING_HOST=${MEET_LISTENING_HOST:?err}
       - LISTENING_PORT=12443
       - TURN_SERVER=${MEET_TURN_SERVER}
       - TURN_STATIC_SECRET=${COTURN_STATIC_SECRET}
       - AUTH_TOKEN=${MEET_SERVER_TOKEN:?err}
       - WEBHOOK_TOKEN=${MEET_WEBHOOK_TOKEN:?err}
       - WEBHOOK_URL=${APP_PUBLIC_URL:?err}/api/webhooks/meet
       - SSL_CERT=/etc/pki/tls/certs/meet.${APP_WEBSITE_DOMAIN:?err}.cert
       - SSL_KEY=/etc/pki/tls/private/meet.${APP_WEBSITE_DOMAIN:?err}.key
     network_mode: host
     container_name: kolab-meet
     image: kolab-meet
     volumes:
       - ./meet/server:/src/meet/:ro
       - ./docker/certs/meet.${APP_WEBSITE_DOMAIN}.cert:/etc/pki/tls/certs/meet.${APP_WEBSITE_DOMAIN}.cert
       - ./docker/certs/meet.${APP_WEBSITE_DOMAIN}.key:/etc/pki/tls/private/meet.${APP_WEBSITE_DOMAIN}.key
 networks:
   kolab:
     driver: bridge
     ipam:
       config:
         - subnet: "172.18.0.0/24"
 volumes:
   mariadb:
   imap:
   ldap:
diff --git a/docker/kolab/kolab.conf b/docker/kolab/kolab.conf
index b320e19e..4a8ff5b5 100644
--- a/docker/kolab/kolab.conf
+++ b/docker/kolab/kolab.conf
@@ -1,83 +1,83 @@
 [kolab]
 primary_domain = mgmt.com
 auth_mechanism = ldap
 imap_backend = cyrus-imap
 default_locale = en_US
 sync_interval = 300
 domain_sync_interval = 600
 policy_uid = %(surname)s.lower()
 daemon_rcpt_policy = False
 [imap]
 virtual_domains = userid
 
 [ldap]
 ldap_uri = ldap://127.0.0.1:389
 timeout = 10
 supported_controls = 0,2,3
 base_dn = dc=mgmt,dc=com
 bind_dn = cn=Directory Manager
-bind_pw = Welcome2KolabSystems
+bind_pw = 
 service_bind_dn = uid=kolab-service,ou=Special Users,dc=mgmt,dc=com
-service_bind_pw = Welcome2KolabSystems
+service_bind_pw = 
 user_base_dn = dc=hosted,dc=com
 user_scope = sub
 user_filter = (objectclass=inetorgperson)
 kolab_user_base_dn = dc=hosted,dc=com
 kolab_user_filter = (objectclass=kolabinetorgperson)
 group_base_dn = dc=hosted,dc=com
 group_filter = (|(objectclass=groupofuniquenames)(objectclass=groupofurls))
 group_scope = sub
 kolab_group_filter = (|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgroupofurls))
 sharedfolder_base_dn = dc=hosted,dc=com
 sharedfolder_filter = (objectclass=kolabsharedfolder)
 sharedfolder_acl_entry_attribute = acl
 resource_base_dn = dc=hosted,dc=com
 resource_filter = (|%(group_filter)s(objectclass=kolabsharedfolder))
 domain_base_dn = ou=Domains,dc=mgmt,dc=com
 domain_filter = (&(associatedDomain=*))
 domain_name_attribute = associateddomain
 domain_rootdn_attribute = inetdomainbasedn
 quota_attribute = mailquota
 modifytimestamp_format = %Y%m%d%H%M%SZ
 unique_attribute = nsuniqueid
 mail_attributes = mail, alias
 mailserver_attribute = mailhost
 auth_attributes = mail, uid
 
 [kolab_smtp_access_policy]
-cache_uri = mysql://kolab:Welcome2KolabSystems@mariadb/kolab
+cache_uri = mysql://kolab:@mariadb/kolab
 cache_retention = 86400
 address_search_attrs = mail, alias
 delegate_sender_header = True
 alias_sender_header = True
 sender_header = True
 xsender_header = True
 empty_sender_hosts = 3.2.1.0/24, 6.6.6.0/24
 
 [kolab_wap]
 mgmt_root_dn = dc=mgmt,dc=com
 hosted_root_dn = dc=hosted,dc=com
 api_url = http://127.0.0.1:9080/kolab-webadmin/api
 skin = default
-sql_uri = mysql://kolab:Welcome2KolabSystems@mariadb/kolab
+sql_uri = mysql://kolab:@mariadb/kolab
 ssl_verify_peer = false
 ssl_verify_host = false
 
 [cyrus-imap]
 uri = imaps://127.0.0.1:11993
 admin_login = cyrus-admin
-admin_password = Welcome2KolabSystems
+admin_password = 
 
 [cyrus-sasl]
 result_attribute = mail
 
 [wallace]
 webmail_url = https://%(domain)s/roundcubemail
 modules = resources, invitationpolicy
 kolab_invitation_policy = ACT_ACCEPT_IF_NO_CONFLICT:example.org, ACT_MANUAL
 invitationpolicy_autoupdate_other_attendees_on_reply = false
 resource_calendar_expire_days = 100
 
 [mgmt.com]
 default_quota = 1048576
 daemon_rcpt_policy = False
diff --git a/docker/kolab/utils/settings.sh b/docker/kolab/utils/settings.sh
index fbf6f045..142d7aa6 100755
--- a/docker/kolab/utils/settings.sh
+++ b/docker/kolab/utils/settings.sh
@@ -1,24 +1,22 @@
 #!/bin/bash
 
 export rootdn=${LDAP_ADMIN_ROOT_DN:-"dc=mgmt,dc=com"}
 export domain=${DOMAIN:-"mgmt.com"}
 export domain_db=${DOMAIN_DB:-"mgmt_com"}
 export ldap_host=${LDAP_HOST}
-export ldap_binddn=${LDAP_ADMIN_BIND_DN:-"cn=Directory Manager"}
-export ldap_bindpw=${LDAP_ADMIN_BIND_PW:-"Welcome2KolabSystems"}
+export ldap_binddn=${LDAP_ADMIN_BIND_DN}
+export ldap_bindpw=${LDAP_ADMIN_BIND_PW}
 
-export cyrus_admin=${IMAP_ADMIN_LOGIN:-"cyrus-admin"}
+export cyrus_admin=${IMAP_ADMIN_LOGIN}
 
 export imap_host=${IMAP_HOST}
-export cyrus_admin_pw=${IMAP_ADMIN_PASSWORD:-"Welcome2KolabSystems"}
+export cyrus_admin_pw=${IMAP_ADMIN_PASSWORD}
 
-export kolab_service_pw=${LDAP_SERVICE_BIND_PW:-"Welcome2KolabSystems"}
-export hosted_kolab_service_pw=${LDAP_HOSTED_BIND_PW:-"Welcome2KolabSystems"}
+export kolab_service_pw=${LDAP_SERVICE_BIND_PW}
+export hosted_kolab_service_pw=${LDAP_HOSTED_BIND_PW}
 
 export hosted_domain=${HOSTED_DOMAIN:-"hosted.com"}
 export hosted_domain_db=${HOSTED_DOMAIN_DB:-"hosted_com"}
 export hosted_domain_rootdn=${LDAP_HOSTED_ROOT_DN:-"dc=hosted,dc=com"}
 
 export domain_base_dn=${LDAP_DOMAIN_BASE_DN:-"ou=Domains,dc=mgmt,dc=com"}
-
-export default_user_password=${DEFAULT_USER_PASSWORD:-"Welcome2KolabSystems"}
diff --git a/docker/pdns/Dockerfile b/docker/pdns/Dockerfile
index a75296c6..731ed9c3 100644
--- a/docker/pdns/Dockerfile
+++ b/docker/pdns/Dockerfile
@@ -1,37 +1,48 @@
 FROM fedora:35
 
 ENV container docker
 ENV SYSTEMD_PAGER=''
 
 RUN dnf -y install \
     --setopt 'tsflags=nodocs' \
     bind-utils \
     iproute \
     iptables \
     iputils \
     net-tools \
     pdns \
     pdns-backend-mysql \
     pdns-recursor \
     pdns-tools \
     procps-ng \
     vim-enhanced \
     wget \
     which && \
     dnf clean all
 
 COPY pdns.conf /etc/pdns/pdns.conf
 COPY recursor.conf /etc/pdns-recursor/recursor.conf
 
+ARG DB_HOST
+ARG DB_DATABASE
+ARG DB_USERNAME
+ARG DB_PASSWORD
+RUN sed -i -r \
+    -e "s|DB_HOST|$DB_HOST|g" \
+    -e "s|DB_DATABASE|$DB_DATABASE|g" \
+    -e "s|DB_USERNAME|$DB_USERNAME|g" \
+    -e "s|DB_PASSWORD|$DB_PASSWORD|g" \
+    /etc/pdns/pdns.conf
+
 RUN systemctl disable systemd-resolved && systemctl enable pdns  && systemctl enable pdns-recursor
 
 # This is how we could run pdns without systemd
 # ENV PDNS_guardian=yes \
 #     PDNS_setuid=pdns \
 #     PDNS_setgid=pdns \
 #     PDNS_launch=gmysql
 
 # CMD ["/usr/sbin/pdns_server", "--guardian=no", "--daemon=no", "--disable-syslog", "--log-timestamp=no", "--write-pid=no"]
 CMD ["/lib/systemd/systemd", "--system"]
 
 EXPOSE 53 53/udp
diff --git a/docker/pdns/pdns.conf b/docker/pdns/pdns.conf
index 07ecf0f0..dabe3665 100644
--- a/docker/pdns/pdns.conf
+++ b/docker/pdns/pdns.conf
@@ -1,69 +1,69 @@
 launch=gmysql
 log-dns-details
 query-logging=yes
 
 local-address=127.0.0.1:5300
 
 edns-subnet-processing
 
-gmysql-host=mariadb
-gmysql-dbname=kolabdev
-gmysql-password=kolab
-gmysql-user=kolabdev
+gmysql-host=DB_HOST
+gmysql-dbname=DB_DATABASE
+gmysql-password=DB_PASSWORD
+gmysql-user=DB_USERNAME
 
 gmysql-activate-domain-key-query=UPDATE powerdns_cryptokeys SET active=1 WHERE domain_id=(SELECT id FROM domains WHERE name=?) AND powerdns_cryptokeys.id=?
 gmysql-add-domain-key-query=INSERT INTO powerdns_cryptokeys (domain_id, flags, active, content) SELECT id, ?, ?, ? FROM powerdns_domains WHERE name=?
 gmysql-any-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND name=? AND domain_id=?
 gmysql-any-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND name=?
 gmysql-basic-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND type=? AND name=?
 gmysql-clear-domain-all-keys-query=delete FROM powerdns_cryptokeys WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?)
 gmysql-clear-domain-all-metadata-query=delete FROM powerdns_domain_settings WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?)
 gmysql-clear-domain-metadata-query=delete FROM powerdns_domain_settings WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_domain_settings.kind=?
 gmysql-deactivate-domain-key-query=UPDATE powerdns_cryptokeys SET active=0 WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_cryptokeys.id=?
 gmysql-delete-comment-rrset-query=DELETE FROM powerdns_comments WHERE domain_id=? AND name=? AND type=?
 gmysql-delete-comments-query=DELETE FROM powerdns_comments WHERE domain_id=?
 gmysql-delete-domain-query=delete FROM powerdns_domains WHERE name=?
 gmysql-delete-empty-non-terminal-query=delete FROM powerdns_records WHERE domain_id=? AND name=? AND type is null
 gmysql-delete-names-query=delete FROM powerdns_records WHERE domain_id=? AND name=?
 gmysql-delete-rrset-query=delete FROM powerdns_records WHERE domain_id=? AND name=? AND type=?
 gmysql-delete-tsig-key-query=delete FROM powerdns_tsigkeys WHERE name=?
 gmysql-delete-zone-query=delete FROM powerdns_records WHERE domain_id=?
 gmysql-get-all-domain-metadata-query=SELECT kind,content FROM powerdns_domains, powerdns_domain_settings WHERE powerdns_domain_settings.domain_id=powerdns_domains.id AND name=?
 gmysql-get-all-domains-query=SELECT powerdns_domains.id, powerdns_domains.name, powerdns_records.content, powerdns_domains.type, powerdns_domains.master, powerdns_domains.notified_serial, powerdns_domains.last_check, powerdns_domains.account FROM powerdns_domains LEFT JOIN powerdns_records ON powerdns_records.domain_id=powerdns_domains.id AND powerdns_records.type='SOA' AND powerdns_records.name=powerdns_domains.name WHERE powerdns_records.disabled=0 OR ?
 gmysql-get-domain-metadata-query=SELECT content FROM powerdns_domains, powerdns_domain_settings WHERE powerdns_domain_settings.domain_id=powerdns_domains.id AND name=? AND powerdns_domain_settings.kind=?
 gmysql-get-last-inserted-key-id-query=SELECT LAST_INSERT_ID()
 gmysql-get-order-after-query=SELECT ordername FROM powerdns_records WHERE ordername > ? AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 ASC LIMIT 1
 gmysql-get-order-before-query=SELECT ordername, name FROM powerdns_records WHERE ordername <= ? AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 desc LIMIT 1
 gmysql-get-order-first-query=SELECT ordername FROM powerdns_records WHERE domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 ASC LIMIT 1
 gmysql-get-order-last-query=SELECT ordername, name FROM powerdns_records WHERE ordername != '' AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 desc LIMIT 1
 gmysql-get-tsig-key-query=SELECT algorithm, secret FROM powerdns_tsigkeys WHERE name=?
 gmysql-get-tsig-keys-query=SELECT name,algorithm, secret FROM powerdns_tsigkeys
 gmysql-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND type=? AND name=? AND domain_id=?
 gmysql-info-all-master-query=SELECT d.id, d.name, d.notified_serial, r.content FROM powerdns_records r join powerdns_domains d on r.name=d.name WHERE r.type='SOA' AND r.disabled=0 AND d.type='MASTER'
 gmysql-info-all-slaves-query=SELECT id,name,master,last_check FROM powerdns_domains WHERE type='SLAVE'
 gmysql-info-zone-query=SELECT id,name,master,last_check,notified_serial,type,account FROM powerdns_domains WHERE name=?
 gmysql-insert-comment-query=INSERT INTO powerdns_comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)
 gmysql-insert-empty-non-terminal-order-query=INSERT INTO powerdns_records (type,domain_id,disabled,name,ordername,auth,content,ttl,prio) values (null,?,0,?,?,?,NULL,NULL,NULL)
 gmysql-insert-record-query=INSERT INTO powerdns_records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (?,?,?,?,?,?,?,?,?)
 gmysql-insert-zone-query=INSERT INTO powerdns_domains (type,name,master,account,last_check,notified_serial) values(?,?,?,?,NULL,NULL)
 gmysql-list-comments-query=SELECT domain_id,name,type,modified_at,account,comment FROM powerdns_comments WHERE domain_id=?
 gmysql-list-domain-keys-query=SELECT powerdns_cryptokeys.id, flags, active, content FROM powerdns_domains, powerdns_cryptokeys WHERE powerdns_cryptokeys.domain_id=powerdns_domains.id AND name=?
 gmysql-list-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE (disabled=0 OR ?) AND domain_id=? ORDER BY name, type
 gmysql-list-subzone-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND (name=? OR name like ?) AND domain_id=?
 gmysql-nullify-ordername-and-update-auth-query=UPDATE powerdns_records SET ordername=NULL,auth=? WHERE domain_id=? AND name=? AND disabled=0
 gmysql-nullify-ordername-and-update-auth-type-query=UPDATE powerdns_records SET ordername=NULL,auth=? WHERE domain_id=? AND name=? AND type=? AND disabled=0
 gmysql-remove-domain-key-query=delete FROM powerdns_cryptokeys WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_cryptokeys.id=?
 gmysql-remove-empty-non-terminals-from-zone-query=delete FROM powerdns_records WHERE domain_id=? AND type is null
 gmysql-search-comments-query=SELECT domain_id,name,type,modified_at,account,comment FROM powerdns_comments WHERE name LIKE ? OR comment LIKE ? LIMIT ?
 gmysql-search-records-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE name LIKE ? OR content LIKE ? LIMIT ?
 gmysql-set-domain-metadata-query=INSERT INTO powerdns_domain_settings (domain_id, kind, content) SELECT id, ?, ? FROM powerdns_domains WHERE name=?
 gmysql-set-tsig-key-query=REPLACE INTO powerdns_tsigkeys (name,algorithm,secret) values(?,?,?)
 gmysql-supermaster-query=SELECT account FROM powerdns_masters WHERE ip=? AND nameserver=?
 gmysql-update-account-query=UPDATE powerdns_domains SET account=? WHERE name=?
 gmysql-update-kind-query=UPDATE powerdns_domains SET type=? WHERE name=?
 gmysql-update-lastcheck-query=UPDATE powerdns_domains SET last_check=? WHERE id=?
 gmysql-update-master-query=UPDATE powerdns_domains SET master=? WHERE name=?
 gmysql-update-ordername-and-auth-query=UPDATE powerdns_records SET ordername=?,auth=? WHERE domain_id=? AND name=? AND disabled=0
 gmysql-update-ordername-and-auth-type-query=UPDATE powerdns_records SET ordername=?,auth=? WHERE domain_id=? AND name=? AND type=? AND disabled=0
 gmysql-update-serial-query=UPDATE powerdns_domains SET notified_serial=? WHERE id=?
 
diff --git a/docker/tests/init.sh b/docker/tests/init.sh
index 8d0c083f..9e0cad1e 100755
--- a/docker/tests/init.sh
+++ b/docker/tests/init.sh
@@ -1,80 +1,81 @@
 #!/bin/bash
 #set -e
+rm -rf /src/kolabsrc
 sudo cp -a /src/kolabsrc.orig /src/kolabsrc
 sudo chmod 777 -R /src/kolabsrc
 cd /src/kolabsrc
 
 sudo rm -rf vendor/ composer.lock
 php -dmemory_limit=-1 $(command -v composer) install
 sudo rm -rf node_modules
 mkdir node_modules
 npm install
 find bootstrap/cache/ -type f ! -name ".gitignore" -delete
 ./artisan key:generate
 ./artisan clear-compiled
 ./artisan cache:clear
 ./artisan horizon:install
 
 if [ ! -f storage/oauth-public.key -o ! -f storage/oauth-private.key ]; then
     ./artisan passport:keys --force
 fi
 
 cat >> .env << EOF
 PASSPORT_PRIVATE_KEY="$(cat storage/oauth-private.key)"
 PASSPORT_PUBLIC_KEY="$(cat storage/oauth-public.key)"
 EOF
 
 if rpm -qv chromium 2>/dev/null; then
     chver=$(rpmquery --queryformat="%{VERSION}" chromium | awk -F'.' '{print $1}')
     ./artisan dusk:chrome-driver ${chver}
 fi
 
 if [ ! -f 'resources/countries.php' ]; then
     ./artisan data:countries
 fi
 
 npm run dev
 
 # /usr/bin/chromium-browser --no-sandbox --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
 
 rm -rf database/database.sqlite
 ./artisan db:ping --wait
 php -dmemory_limit=512M ./artisan migrate:refresh --seed
 ./artisan data:import || :
 ./artisan queue:work --stop-when-empty
 ./artisan octane:start --host=$(grep OCTANE_HTTP_HOST .env | tail -n1 | sed "s/OCTANE_HTTP_HOST=//") >/dev/null 2>&1 &
 
 if [ "$1" == "testsuite" ]; then
     php \
         -dmemory_limit=-1 \
         vendor/bin/phpunit \
         --exclude-group skipci \
         --verbose \
         --stop-on-defect \
         --stop-on-error \
         --stop-on-failure \
         --testsuite Unit
 
     php \
         -dmemory_limit=-1 \
         vendor/bin/phpunit \
         --exclude-group skipci \
         --verbose \
         --stop-on-defect \
         --stop-on-error \
         --stop-on-failure \
         --testsuite Functional
 
     php \
         -dmemory_limit=-1 \
         vendor/bin/phpunit \
         --exclude-group skipci,coinbase,mollie,stripe,meet,dns \
         --verbose \
         --stop-on-defect \
         --stop-on-error \
         --stop-on-failure \
         --testsuite Feature
 fi
 if [ "$1" == "shell" ]; then
     exec /bin/bash
 fi
diff --git a/docker/webapp/init.sh b/docker/webapp/init.sh
index 7c4c879a..5c893297 100755
--- a/docker/webapp/init.sh
+++ b/docker/webapp/init.sh
@@ -1,39 +1,29 @@
 #!/bin/bash
 set -e
 rm -rf /src/kolabsrc
 cp -a /src/kolabsrc.orig /src/kolabsrc
 cd /src/kolabsrc
 
 rm -rf vendor/ composer.lock .npm storage/framework
 mkdir -p storage/framework/{sessions,views,cache}
 
 php -dmemory_limit=-1 $(command -v composer) install
 npm install
 find bootstrap/cache/ -type f ! -name ".gitignore" -delete
 ./artisan key:generate
 ./artisan storage:link
 ./artisan clear-compiled
 ./artisan cache:clear
 ./artisan horizon:install
 
-if [ ! -f storage/oauth-public.key -o ! -f storage/oauth-private.key ]; then
-    ./artisan passport:keys --force
-fi
-
-cat >> .env << EOF
-PASSPORT_PRIVATE_KEY="$(cat storage/oauth-private.key)"
-PASSPORT_PUBLIC_KEY="$(cat storage/oauth-public.key)"
-EOF
-
 if [ ! -f 'resources/countries.php' ]; then
     ./artisan data:countries
 fi
 
 npm run dev
 
-rm -rf database/database.sqlite
 ./artisan db:ping --wait
 php -dmemory_limit=512M ./artisan migrate:refresh --seed
 ./artisan data:import || :
 nohup ./artisan horizon >/dev/null 2>&1 &
 ./artisan octane:start --host=$(grep OCTANE_HTTP_HOST .env | tail -n1 | sed "s/OCTANE_HTTP_HOST=//")
diff --git a/src/.gitignore b/src/.gitignore
index 9be9b340..53f771ba 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1,27 +1,29 @@
 *.swp
 database/database.sqlite
 node_modules/
 package-lock.json
 public/css/*.css
 public/hot
 public/js/*.js
 public/storage/
 storage/*.key
 storage/*.log
 storage/*-????-??-??*
 storage/export/
 tests/report/
 vendor
 .env
 .env.backup
 .env.local
 .env.testing
 .phpunit.result.cache
 Homestead.json
 Homestead.yaml
 npm-debug.log
 yarn-error.log
 composer.lock
 resources/countries.php
 resources/build/js/
+database/seeds/
+src/public/themes/active
 cache
diff --git a/src/.s2i/bin/assemble b/src/.s2i/bin/assemble
index 30ca417c..f6248547 100755
--- a/src/.s2i/bin/assemble
+++ b/src/.s2i/bin/assemble
@@ -1,45 +1,38 @@
 #!/bin/bash
 
 set -x
 set -e
 
 function composer_install () {
     echo "--->> Detected composer.json, running install"
     php -dmemory_limit=${COMPOSER_MEMORY_LIMIT:--1} /usr/bin/composer install ${COMPOSER_ARGS}
     rm -rf ~/.cache/composer/
 }
 
 shopt -s dotglob
 echo "--->> $(rm -vrf vendor/ composer.lock)"
 
 echo "---> Installing application source..."
 rm -fR /tmp/src/.git
 mv /tmp/src/* ./
 
 pushd /opt/app-root/src
 
 fix-permissions ./
 
-if [ -f ".env.local" ]; then
-    # Ensure there's a line ending
-    echo "---->> Append .env.local"
-    echo "" >> .env
-    cat .env.local >> .env
-fi
-
 if [ -f "composer.json" ]; then
     echo "--->> Detected composer.json, running install"
     composer_install
 fi
 
 ./artisan horizon:install
 
 if [ ! -z "${OPENEXCHANGERATES_API_KEY}" ]; then
     ./artisan data:import:open-exchange-rates
 fi
 
 echo "---->> Run npm run prod"
 npm install
 npm run ${LARAVEL_ENV:=prod} && rm -rf ~/.npm/
 
 fix-permissions ./
diff --git a/src/database/migrations/2021_04_08_150000_signup_code_headers.php b/src/database/migrations/2021_04_08_150000_signup_code_headers.php
index 037b8298..8f857b13 100644
--- a/src/database/migrations/2021_04_08_150000_signup_code_headers.php
+++ b/src/database/migrations/2021_04_08_150000_signup_code_headers.php
@@ -1,40 +1,39 @@
 <?php
 
 use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Schema;
 
 // phpcs:ignore
 class SignupCodeHeaders extends Migration
 {
     /**
      * Run the migrations.
      *
      * @return void
      */
     public function up()
     {
         Schema::table(
             'signup_codes',
             function (Blueprint $table) {
                 $table->text('headers')->nullable();
             }
         );
     }
 
     /**
      * Reverse the migrations.
      *
      * @return void
      */
     public function down()
     {
         Schema::table(
             'signup_codes',
             function (Blueprint $table) {
                 $table->dropColumn('headers');
             }
         );
     }
 }
diff --git a/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php b/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php
index 46a6d3bc..32073fc2 100644
--- a/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php
+++ b/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php
@@ -1,45 +1,44 @@
 <?php
 
 use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Schema;
 
 // phpcs:ignore
 class CreateTenantSettingsTable extends Migration
 {
     /**
      * Run the migrations.
      *
      * @return void
      */
     public function up()
     {
         Schema::create(
             'tenant_settings',
             function (Blueprint $table) {
                 $table->bigIncrements('id');
                 $table->unsignedBigInteger('tenant_id');
                 $table->string('key');
                 $table->text('value');
                 $table->timestamp('created_at')->useCurrent();
                 $table->timestamp('updated_at')->useCurrent();
 
                 $table->foreign('tenant_id')->references('id')->on('tenants')
                     ->onDelete('cascade')->onUpdate('cascade');
 
                 $table->unique(['tenant_id', 'key']);
             }
         );
     }
 
     /**
      * Reverse the migrations.
      *
      * @return void
      */
     public function down()
     {
         Schema::dropIfExists('tenant_settings');
     }
 }
diff --git a/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php b/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php
index ac3ea22d..16544c3b 100644
--- a/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php
+++ b/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php
@@ -1,156 +1,121 @@
 <?php
 
 use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
 return new class extends Migration
 {
     /**
      * Run the migrations.
      */
     public function up(): void
     {
         Schema::create(
             'permissions',
             function (Blueprint $table) {
                 $table->string('id', 36)->primary();
                 $table->bigInteger('permissible_id');
                 $table->string('permissible_type');
                 $table->integer('rights')->default(0);
                 $table->string('user');
                 $table->timestamps();
 
                 $table->index('user');
                 $table->index(['permissible_id', 'permissible_type']);
             }
         );
 
         Schema::table(
             'openvidu_rooms',
             function (Blueprint $table) {
                 $table->bigInteger('tenant_id')->unsigned()->nullable();
                 $table->string('description')->nullable();
                 $table->softDeletes();
 
                 $table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('set null');
             }
         );
 
         // Create the new SKUs
-        if (!\App\Sku::where('title', 'room')->first()) {
-            $sku = \App\Sku::create([
-                    'title' => 'group-room',
-                    'name' => 'Group conference room',
-                    'description' => 'Shareable audio & video conference room',
-                    'cost' => 0,
-                    'units_free' => 0,
-                    'period' => 'monthly',
-                    'handler_class' => 'App\Handlers\GroupRoom',
-                    'active' => true,
-            ]);
+        $sku = \App\Sku::where('title', 'room')->first();
 
-            $sku = \App\Sku::create([
-                    'title' => 'room',
-                    'name' => 'Standard conference room',
-                    'description' => 'Audio & video conference room',
-                    'cost' => 0,
-                    'units_free' => 0,
-                    'period' => 'monthly',
-                    'handler_class' => 'App\Handlers\Room',
-                    'active' => true,
-            ]);
+        // Create the entitlement for every existing room
+        foreach (\App\Meet\Room::get() as $room) {
+            $user = \App\User::find($room->user_id); // @phpstan-ignore-line
+            if (!$user) {
+                $room->forceDelete();
+                continue;
+            }
 
-            // Create the entitlement for every existing room
-            foreach (\App\Meet\Room::get() as $room) {
-                $user = \App\User::find($room->user_id); // @phpstan-ignore-line
-                if (!$user) {
-                    $room->forceDelete();
-                    continue;
-                }
-
-                // Set tenant_id
-                if ($user->tenant_id) {
-                    $room->tenant_id = $user->tenant_id;
-                    $room->save();
-                }
-
-                $wallet = $user->wallets()->first();
-
-                \App\Entitlement::create([
-                        'wallet_id' => $wallet->id,
-                        'sku_id' => $sku->id,
-                        'cost' => 0,
-                        'fee' => 0,
-                        'entitleable_id' => $room->id,
-                        'entitleable_type' => \App\Meet\Room::class
-                ]);
+            // Set tenant_id
+            if ($user->tenant_id) {
+                $room->tenant_id = $user->tenant_id;
+                $room->save();
             }
+
+            $wallet = $user->wallets()->first();
+
+            \App\Entitlement::create([
+                    'wallet_id' => $wallet->id,
+                    'sku_id' => $sku->id,
+                    'cost' => 0,
+                    'fee' => 0,
+                    'entitleable_id' => $room->id,
+                    'entitleable_type' => \App\Meet\Room::class
+            ]);
         }
 
         // Remove 'meet' SKU/entitlements
         \App\Sku::where('title', 'meet')->delete();
 
         Schema::table(
             'openvidu_rooms',
             function (Blueprint $table) {
                 $table->dropForeign('openvidu_rooms_user_id_foreign');
                 $table->dropColumn('user_id');
             }
         );
     }
 
     /**
      * Reverse the migrations.
      */
     public function down(): void
     {
         Schema::table(
             'openvidu_rooms',
             function (Blueprint $table) {
                 $table->dropForeign('openvidu_rooms_tenant_id_foreign');
                 $table->dropColumn('tenant_id');
                 $table->dropColumn('description');
 
                 $table->bigInteger('user_id')->nullable();
                 $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
             }
         );
 
         // Set user_id back
         foreach (\App\Meet\Room::get() as $room) {
             $wallet = $room->wallet();
             if (!$wallet) {
                 $room->forceDelete();
                 continue;
             }
 
             $room->user_id = $wallet->user_id; // @phpstan-ignore-line
             $room->save();
         }
 
         Schema::table(
             'openvidu_rooms',
             function (Blueprint $table) {
                 $table->dropSoftDeletes();
             }
         );
 
         \App\Entitlement::where('entitleable_type', \App\Meet\Room::class)->forceDelete();
-        \App\Sku::where('title', 'room')->delete();
-        \App\Sku::where('title', 'group-room')->delete();
-
-        \App\Sku::create([
-                'title' => 'meet',
-                'name' => 'Voice & Video Conferencing (public beta)',
-                'description' => 'Video conferencing tool',
-                'cost' => 0,
-                'units_free' => 0,
-                'period' => 'monthly',
-                'handler_class' => 'App\Handlers\Meet',
-                'active' => true,
-        ]);
 
         Schema::dropIfExists('permissions');
     }
 };
diff --git a/src/database/migrations/2022_09_08_100000_plans_free_months.php b/src/database/migrations/2022_09_08_100000_plans_free_months.php
index 06ef6881..846a5a09 100644
--- a/src/database/migrations/2022_09_08_100000_plans_free_months.php
+++ b/src/database/migrations/2022_09_08_100000_plans_free_months.php
@@ -1,41 +1,38 @@
 <?php
 
 use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Schema;
 
 return new class extends Migration
 {
     /**
      * Run the migrations.
      *
      * @return void
      */
     public function up()
     {
         Schema::table(
             'plans',
             function (Blueprint $table) {
                 $table->tinyInteger('free_months')->unsigned()->default(0);
             }
         );
-
-        DB::table('plans')->update(['free_months' => 1]);
     }
 
     /**
      * Reverse the migrations.
      *
      * @return void
      */
     public function down()
     {
         Schema::table(
             'plans',
             function (Blueprint $table) {
                 $table->dropColumn('free_months');
             }
         );
     }
 };
diff --git a/src/database/seeds/DatabaseSeeder.php b/src/database/seeds/DatabaseSeeder.php
deleted file mode 100644
index eaec2738..00000000
--- a/src/database/seeds/DatabaseSeeder.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-use Illuminate\Database\Seeder;
-
-// phpcs:ignore
-class DatabaseSeeder extends Seeder
-{
-    /**
-     * Seed the application's database.
-     *
-     * @return void
-     */
-    public function run()
-    {
-        // Define seeders order
-        $seeders = [
-            'IP4NetSeeder',
-            'TenantSeeder',
-            'DiscountSeeder',
-            'DomainSeeder',
-            'SkuSeeder',
-            'PackageSeeder',
-            'PlanSeeder',
-            'PowerDNSSeeder',
-            'UserSeeder',
-            'OauthClientSeeder',
-            'ResourceSeeder',
-            'SharedFolderSeeder',
-            'MeetRoomSeeder'
-        ];
-
-        $env = ucfirst(App::environment());
-
-        // Check if the seeders exists
-        foreach ($seeders as $idx => $name) {
-            $class = "Database\\Seeds\\$env\\$name";
-            $seeders[$idx] = class_exists($class) ? $class : null;
-        }
-
-        $seeders = array_filter($seeders);
-
-        $this->call($seeders);
-    }
-}
diff --git a/src/database/seeds/production/DiscountSeeder.php b/src/database/seeds/production/DiscountSeeder.php
deleted file mode 100644
index d5b83872..00000000
--- a/src/database/seeds/production/DiscountSeeder.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-namespace Database\Seeds\Production;
-
-use App\Discount;
-use Illuminate\Database\Seeder;
-
-class DiscountSeeder extends Seeder
-{
-    /**
-     * Run the database seeds.
-     *
-     * @return void
-     */
-    public function run()
-    {
-        Discount::create(
-            [
-                'description' => 'Free Account',
-                'discount' => 100,
-                'active' => true,
-            ]
-        );
-
-        Discount::create(
-            [
-                'description' => 'Student or Educational Institution',
-                'discount' => 30,
-                'active' => true,
-            ]
-        );
-    }
-}
diff --git a/src/database/seeds/production/DomainSeeder.php b/src/database/seeds/production/DomainSeeder.php
deleted file mode 100644
index 59f8ee5e..00000000
--- a/src/database/seeds/production/DomainSeeder.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-namespace Database\Seeds\Production;
-
-use App\Domain;
-use Illuminate\Database\Seeder;
-
-class DomainSeeder extends Seeder
-{
-    /**
-     * Run the database seeds.
-     *
-     * @return void
-     */
-    public function run()
-    {
-        $domains = [
-            "attorneymail.ch",
-            "barmail.ch",
-            "collaborative.li",
-            "diplomail.ch",
-            "freedommail.ch",
-            "groupoffice.ch",
-            "journalistmail.ch",
-            "legalprivilege.ch",
-            "libertymail.co",
-            "libertymail.net",
-            "kolabnow.com",
-            "kolabnow.ch",
-            "mailatlaw.ch",
-            "medmail.ch",
-            "mykolab.ch",
-            "mykolab.com",
-            "myswissmail.ch",
-            "opengroupware.ch",
-            "pressmail.ch",
-            "swissgroupware.ch",
-            "switzerlandmail.ch",
-            "trusted-legal-mail.ch",
-        ];
-
-        foreach ($domains as $domain) {
-            Domain::create(
-                [
-                    'namespace' => $domain,
-                    'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
-                    'type' => Domain::TYPE_PUBLIC
-                ]
-            );
-        }
-    }
-}
diff --git a/src/database/seeds/production/PackageSeeder.php b/src/database/seeds/production/PackageSeeder.php
deleted file mode 100644
index 2500aa23..00000000
--- a/src/database/seeds/production/PackageSeeder.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-
-namespace Database\Seeds\Production;
-
-use App\Package;
-use App\Sku;
-use Illuminate\Database\Seeder;
-
-class PackageSeeder extends Seeder
-{
-    /**
-     * Run the database seeds.
-     *
-     * @return void
-     */
-    public function run()
-    {
-        $skuActiveSync = Sku::firstOrCreate(['title' => 'activesync']);
-        $skuGroupware = Sku::firstOrCreate(['title' => 'groupware']);
-        $skuMailbox = Sku::firstOrCreate(['title' => 'mailbox']);
-        $skuStorage = Sku::firstOrCreate(['title' => 'storage']);
-
-        $package = Package::create(
-            [
-                'title' => 'kolab',
-                'name' => 'Groupware Account',
-                'description' => 'A fully functional groupware account.',
-                'discount_rate' => 0
-            ]
-        );
-
-        $skus = [
-            $skuMailbox,
-            $skuGroupware,
-            $skuStorage,
-            $skuActiveSync
-        ];
-
-        $package->skus()->saveMany($skus);
-
-        // This package contains 2 units of the storage SKU, which just so happens to also
-        // be the number of SKU free units.
-        $package->skus()->updateExistingPivot(
-            $skuStorage,
-            ['qty' => 2],
-            false
-        );
-
-        $package = Package::create(
-            [
-                'title' => 'lite',
-                'name' => 'Lite Account',
-                'description' => 'Just mail and no more.',
-                'discount_rate' => 0
-            ]
-        );
-
-        $skus = [
-            $skuMailbox,
-            $skuStorage
-        ];
-
-        $package->skus()->saveMany($skus);
-
-        $package->skus()->updateExistingPivot(
-            Sku::firstOrCreate(['title' => 'storage']),
-            ['qty' => 2],
-            false
-        );
-
-        $package = Package::create(
-            [
-                'title' => 'domain-hosting',
-                'name' => 'Domain Hosting',
-                'description' => 'Use your own, existing domain.',
-                'discount_rate' => 0
-            ]
-        );
-
-        $skus = [
-            Sku::firstOrCreate(['title' => 'domain-hosting'])
-        ];
-
-        $package->skus()->saveMany($skus);
-    }
-}
diff --git a/src/database/seeds/production/PlanSeeder.php b/src/database/seeds/production/PlanSeeder.php
deleted file mode 100644
index 35c35ef8..00000000
--- a/src/database/seeds/production/PlanSeeder.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-namespace Database\Seeds\Production;
-
-use App\Package;
-use App\Plan;
-use Illuminate\Database\Seeder;
-
-class PlanSeeder extends Seeder
-{
-    /**
-     * Run the database seeds.
-     *
-     * @return void
-     */
-    public function run()
-    {
-        $description = <<<'EOD'
-<p>Everything you need to get started or try Kolab Now, including:</p>
-<ul>
-    <li>Perfect for anyone wanting to move to Kolab Now</li>
-    <li>Suite of online apps: Secure email, calendar, address book, files and more</li>
-    <li>Access for anywhere: Sync all your devices to your Kolab Now account</li>
-    <li>Secure hosting: Managed right here on our own servers in Switzerland </li>
-    <li>Start protecting your data today, no ads, no crawling, no compromise</li>
-    <li>An ideal replacement for services like Gmail, Office 365, etc…</li>
-</ul>
-EOD;
-
-        $plan = Plan::create(
-            [
-                'title' => 'individual',
-                'name' => 'Individual Account',
-                'description' => $description,
-                'free_months' => 1,
-                'discount_qty' => 0,
-                'discount_rate' => 0
-            ]
-        );
-
-        $packages = [
-            Package::firstOrCreate(['title' => 'kolab'])
-        ];
-
-        $plan->packages()->saveMany($packages);
-
-        $description = <<<'EOD'
-<p>All the features of the Individual Account, with the following extras:</p>
-<ul>
-    <li>Perfect for anyone wanting to move a group or small business to Kolab Now</li>
-    <li>Recommended to support users from 1 to 100</li>
-    <li>Use your own personal domains with Kolab Now</li>
-    <li>Manage and add users through our online admin area</li>
-    <li>Flexible pricing based on user count</li>
-</ul>
-EOD;
-
-        $plan = Plan::create(
-            [
-                'title' => 'group',
-                'name' => 'Group Account',
-                'description' => $description,
-                'free_months' => 1,
-                'discount_qty' => 0,
-                'discount_rate' => 0
-            ]
-        );
-
-        $packages = [
-            Package::firstOrCreate(['title' => 'domain-hosting']),
-            Package::firstOrCreate(['title' => 'kolab']),
-        ];
-
-        $plan->packages()->saveMany($packages);
-    }
-}