Skip to content

Virtual Server

This guide explains how to deploy a Brezel instance to a (virtual) server. For this guide, we assume the base domain is called example.io and Brezel will be accessible under brezel.example.io. The example IP of the server is given as 5.35.243.342.

Prerequisites

  • A Brezel based on a recent version of brezel/api. i.e. brezel/api@1.0.0 or higher.
  • GitLab repository for that Brezel instance
  • An Ubuntu or Debian based server satisfying the requirements
  • sudo access on that machine

Basic server setup

On a brand new, empty server

If this is a new server with nothing else on it, you should create a new system user with sudo privileges.

This user will not run Brezel, but will be used to set up the server and any administrative tasks. (Using root for this is not recommended.)

Change the name of that user to your liking, we will use “kibro” here.

Terminal window
sudo adduser kibro
sudo usermod -aG sudo kibro

Harden the server

  1. Add a public key for SSH access to the new user.

    First, create a new key pair on your local machine:

    Terminal window
    ssh-keygen -t -C "kibro"

    Make sure to put those keys (public and private) in a safe and secure place. Possibly share them with the team using a password manager (e.g. Passbolt).

    Now, still on your local machine run this with the correct values to copy the public key to the server so you can log in with the private key:

    Terminal window
    ssh-copy-id -i /path/to/the/public/key/to/use.pub kibro@5.35.243.342

    Finally, validate that you can log in with the new user:

    Terminal window
    ssh kibro@5.35.243.342 -i /path/to/the/private/key/to/use

    Additionally, you might want to setup your .ssh/config in a way that allows you to easily connect to the host without specifying the key each time.

    Host your-prefered-name
    HostName 5.35.243.342
    User kibro
    IdentitiesOnly yes
    IdentityFile /path/to/the/private/key/to/use (e.g. ~/.ssh/kibro)
  2. Now disable password SSH based authentication in /etc/ssh/sshd_config:

    Terminal window
    PasswordAuthentication no

    Make sure no file in /etc/ssh/sshd_config.d/ overrides this setting.

    e.g. on Hetzner servers, you need to delete the file /etc/ssh/sshd_config.d/50-cloud-init.conf

    Terminal window
    sudo systemctl restart sshd
  3. Set up a ufw Firewall:

    This will block unwanted traffic to your server.

    We allow SSH, HTTP and HTTPS traffic here. HTTP is only needed for the certificate auto (re)-generation.

    Terminal window
    sudo ufw allow ssh
    sudo ufw allow http
    sudo ufw allow https
    sudo ufw disable
    sudo ufw enable

    The disable / enable dance at the end is needed to restart the firewall with the new rules.

  4. Install and setup CrowdSec or fail2ban:

    Tools like this will block spammy requests to your server and protect it from brute force attacks.

    We will use CrowdSec here, but fail2ban is a good alternative.

    Install it using the latest instructions from the CrowdSec documentation.

    Since we are using ufw, we will install the firewall bouncer for iptables:

    Terminal window
    sudo apt install crowdsec-firewall-bouncer-iptables

    Make sure to check the official documentation for the most up-to-date installation instructions.

    Now you can validate that the bouncer and the engine are running:

    Terminal window
    sudo cscli bouncers list

    To see more detailed infos about what CroudSec is doing, you can check the metrics:

    Terminal window
    sudo cscli metrics

On an already setup server

You fall into this category if you already have a user with sudo access on the server.

Now create a new user for your brezel instance:

Terminal window
sudo adduser brezel

This will be the user that runs and operates the Brezel instance. Most of the actually Brezel related setup in this guide will use this user, only some commands will require sudo and thus the other user.

Rule of thumb: If sudo is NOT used in a command, try it first with brezel, especially for any php commands. (If additional instructions don’t say otherwise.)

Install needed dependencies

Use our sudo user for this (in our case kibro).

  1. Nginx, PHP, PHP extensions, and other necessary dependencies:

    Terminal window
    sudo add-apt-repository ppa:ondrej/php
    sudo apt install nginx
    sudo apt-get install php8.4-cli php8.4-fpm php8.4-curl php8.4-xml php8.4-opcache php8.4-mysql php8.4-zip php8.4-exif php8.4-bcmath php8.4-sockets php8.4-gmp php8.4-imap php8.4-intl php8.4-gd php8.4-mbstring
    sudo apt-get install unzip ghostscript
  2. Imagick:

    This assumes ppa:ondrej/php is available. It should be if you followed the steps above.

    Terminal window
    sudo apt-get install php8.4-imagick
  3. Certbot for SSL certificates: See https://certbot.eff.org/instructions?ws=nginx&os=snap for up-to-date instructions.

    Terminal window
    sudo snap install --classic certbot
    sudo ln -s /snap/bin/certbot /usr/bin/certbot

    If sudo snap install --classic certbot does not work because you are behind some kind of firewall or don’t have access to snap, you can also install certbot via sudo apt install certbot python3-certbot-nginx

  4. Install MariaDB using a guide like this.

    Or follow these rough steps:

    Terminal window
    sudo apt update
    sudo apt install mariadb-server
    sudo mysql_secure_installation

    In the installation process, read closely what it asks you and answer accordingly. You want to remove the test database and disallow remote root login. And you should set a secure password for the root user.

    At this point you can already create a “brezel” database user with the necessary permissions while you are at it.

    CREATE USER 'brezel'@'localhost' IDENTIFIED BY '<your secure password>';
    GRANT ALL ON *.* TO 'brezel'@'localhost' WITH GRANT OPTION;
    FLUSH PRIVILEGES;

Server Performance Tweaks

These are recommended performance tweaks for your server. You can skip them, but your Brezel might not perform as well as it could.

Nginx configuration

Ensure these values in your top level/etc/nginx/nginx.conf file:

worker_processes auto;
events {
worker_connections 2048;
}

If they have already been set, be sure to change the values and not just add a new line with the same directive.

Restart Nginx:

Terminal window
sudo systemctl restart nginx

Configure the Database

Adjust your database configuration in /etc/mysql/my.cnf by adding this block to the very bottom:

[mysqld]
max_connections = 500
innodb_buffer_pool_size = 4G
innodb_log_file_size = 512M
innodb_log_buffer_size = 16M
max_allowed_packet = 64M

If you are running MariaDB add the following options too:

thread_pool_size = 100
query_cache_size = 64M

Now restart the database (Use mysql if you are running MySQL):

Terminal window
sudo systemctl restart mariadb

PHP-FPM configuration

Configure your php-fpm settings in /etc/php/8.4/fpm/pool.d/www.conf:

pm = dynamic
pm.max_children = 300
pm.start_servers = 20
pm.min_spare_servers = 20
pm.max_spare_servers = 50
pm.process_idle_timeout = 15s

You can either just paste these values at the end of the file or search for the values and change them.

PHP configuration

Finally, add the following values to your /etc/php/8.4/fpm/php.ini file:

opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=300
upload_max_filesize = 25M
post_max_size = 25M
memory_limit = 1G
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off

Add DNS records

Login to your external DNS provider and add the following DNS records (replace the bold values with your use case):

NameTypeValue
brezel.example.ioA5.35.243.342
api.brezel.example.ioA5.35.243.342
ws.brezel.example.ioA5.35.243.342

Add the SPA domain

In /etc/nginx/sites-available, create a new file called spa:

server {
listen 80;
listen [::]:80;
server_name brezel.example.io;
location / {
root /var/www/vhosts/api.brezel.example.io/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/vhosts/api.brezel.example.io/dist;
}
}

Save the file, then create a symlink:

Terminal window
sudo ln -s /etc/nginx/sites-available/spa /etc/nginx/sites-enabled/spa

Restart NGINX:

Terminal window
sudo systemctl restart nginx

Finally, secure the domain with an SSL certificate from certbot:

Terminal window
sudo certbot --nginx -d brezel.example.io

Add and configure the API domain

We start off with a few performance tweaks for the server.

Subdomain

Now we create a nginx php-fpm config on the subdomain for the API.

In /etc/nginx/sites-available, create a new file called api:

server {
listen 80;
listen [::]:80;
server_name api.brezel.example.io;
root /var/www/vhosts/api.brezel.example.io/public;
# Increase max upload size
client_max_body_size 25M;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
# Add index.php to the list if you are using PHP
index index.php index.html index.htm index.nginx-debian.html;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
include snippets/fastcgi-php.conf;
}
}

Save the file, then create a symlink:

Terminal window
sudo ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/api

Finally, secure the domain with an SSL certificate from certbot:

Terminal window
sudo certbot --nginx -d api.brezel.example.io

Get Composer ready

Switch to the brezel (su - brezel) user and install composer.

Then use this command to make it executable globally:

Terminal window
mv composer.phar ~/bin/composer

Now switch back to your sudo user (exit)

The Brotcast domain

Brotcast is a websocket server that is used for real-time communication between the Brezel instance and the frontend.

In /etc/nginx/sites-available, create a new file called brotcast:

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name ws.brezel.example.io;
location / {
proxy_http_version 1.1;
# Dynamic Connection header based on upgrade request
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Additional headers for better proxy handling
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header SERVER_PORT $server_port;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8086/; # When hosted via docker compose or laravel reverb
}
}

Save the file, then create a symlink:

Terminal window
sudo ln -s /etc/nginx/sites-available/brotcast /etc/nginx/sites-enabled/brotcast

Restart Nginx:

Terminal window
sudo systemctl restart nginx

Finally, secure the domain with an SSL certificate from certbot:

Terminal window
sudo certbot --nginx -d ws.brezel.example.io;

Prepare for an incoming Brezel

  1. Ensure that you have a user on your server called brezel.

  2. Add your brezel user to the www-data group to ensure the correct permissions for the Nginx web server.

    Terminal window
    sudo usermod -aG www-data brezel
  3. Create the web root directory where your Brezel instance will live

    Terminal window
    sudo mkdir -p /var/www/vhosts/your-brezel
  4. Set the correct initial permissions for the new directory:

    Terminal window
    sudo chown -R brezel:www-data /var/www/vhosts/your-brezel
    sudo chmod -R 774 /var/www/vhosts/your-brezel

Get your Brezel onto the server

Connect to GitLab

This will create SSH keys for the brezel user and add them to GitLab as “deploy keys”. That way, the server can pull your Brezel from the remote repository.

  1. Switch to the brezel user.

    Terminal window
    su - brezel
  2. Generate a new key pair:

    Terminal window
    ssh-keygen -b 4096

    You will be asked for a path. The default is probably fine. Just ensure it is in the home directory of the brezel user (~/.ssh/id_<something>).

    Hit enter.

    You will then be asked for a passphrase. We want no passphrase. Leave the field empty and hit enter.

    Hit enter again to confirm the empty password. It will then output something like this:

    Terminal window
    Your identification has been saved in /home/brezel/.ssh/id_<something>.
    Your public key has been saved in /home/brezel/.ssh/id_<something>.pub.
    The key fingerprint is:
    SHA256:hcsvjIXxM8ZS16X3IHIoGFwPSHr5Cjw35HOIZMEIW1c brezel@server.de
    The key's randomart image is:
    +---[RSA 4096]----+
    |...oo+Eoo . |
    | o...oo+ + o o |
    |. + * + * = o |
    | + = X = o o o |
    | = O S .|
    | + @ + |
    | o o . |
    | . |
    | |
    +----[SHA256]-----+
  3. Now, go to GitLab, to the repository of your Brezel instance and go to Settings > Repository > Deploy Keys. We want our server to be able to read from the GitLab repository. Paste the contents of ~/.ssh/id_<something>.pub to the Key field. Give it a meaningful title like Production [brezel.example.io].

Clone the repository

  1. Move to your new user’s web root directory where we will clone the application repository to.

    Terminal window
    cd /var/www/vhosts/api.brezel.example.io
  2. Install Git if it’s not already installed.

    Terminal window
    sudo apt-get install git

    Clone the instance repository (Get the SSH URL from the GitLab repository):

    Terminal window
    git clone --branch main git@gitlab.kiwis-and-brownies.de:kibro/basedonbrezel/example.git .

Configure file system permissions

Make sure that the instance folder is owned by brezel and www-data with both having permissions to read, write and create files.

We also need to make sure that this ownership is propagated to all subdirectories and files, including newly created files. That’s what the setfacl stuff is for.

Terminal window
sudo apt install acl
sudo chown -R brezel:www-data /var/www/vhosts/api.brezel.example.io
sudo chmod -R 774 /var/www/vhosts/api.brezel.example.io
sudo find /var/www/vhosts/api.brezel.example.io -type d -exec chmod g+s {} \;
sudo setfacl -R -d -m u::rwx,g::rwx,o::rx /var/www/vhosts/api.brezel.example.io
sudo setfacl -R -d -m u:www-data:rwx /var/www/vhosts/api.brezel.example.io
sudo setfacl -R -d -m u:brezel:rwx /var/www/vhosts/api.brezel.example.io

Set up Brezel

In general the Brezel instance will be deployed to the API subdomain. The SPA domain will receive the contents of the minified frontend found in the dist/ folder.

Configure the environment

Ensure you are the brezel user and in the correct directory.

Terminal window
su - brezel
cd /var/www/vhosts/api.brezel.example.io

Copy the .env.example file to .env and adjust the values to your needs.

Terminal window
cp .env.example .env

Follow the comments and instructions in the .env file to set up the environment. Here are some key tips:

  • APP_URL should be set to your API URL, e.g. https://api.brezel.example.io
  • Make sure to set APP_ENV to production and APP_DEBUG to false

Configure Brezel to use the database

Variant 1: Use root credentials
TENANCY_DATABASE="brezel_meta"
TENANCY_USERNAME="root"
TENANCY_PASSWORD="<password>"
Variant 2: Use a privileged database user

Or, if you have root privileges, you can create a brezel MySQL/MariaDB user with ALL privileges:

GRANT ALL ON *.* TO 'brezel'@'localhost' identified by '<password>' WITH GRANT OPTION;
FLUSH PRIVILEGES;

And set the following credentials:

TENANCY_DATABASE="brezel_meta"
TENANCY_USERNAME="brezel"
TENANCY_PASSWORD="<password>"

Install Brezel

Follow the Pipeline guide guide. You can skip step 1 in there as you already generated the keys.

Manual setup, proceed at your own risk Enter your private package credentials:

Only needed if you don’t deploy via a pipeline.

Change $PACKAGE_TOKEN and $PACKAGE_TOKEN_USER to a GitLab token and user that can read brezel/api

Terminal window
php ~/bin/composer config --auth gitlab-token.gitlab.kiwis-and-brownies.de $PACKAGE_TOKEN_USER "$PACKAGE_TOKEN"

Then, install dependencies:

Only needed if you don’t deploy via a pipeline

Terminal window
php ~/bin/composer install

If you want to merge pdf files or use file thumbnails, you need to install ghostscript and the php imagick extension respectively.

Create your system

Now all dependencies should be installed, the needed files for SPA and API should be in place and the environment should be set up.

You can now create your system by its name by running the following command:

Terminal window
php bakery system:create kab

Finally, run the setup commands:

Terminal window
php bakery init --force
php bakery migrate --force
php bakery apply
php bakery load --force
php bakery make:supervisor

After this, a basic Brezel should be up and running. For the full functionality, you need to set up additional services.

Set up additional services

Setting up the brotcast environment

Installation

Available since: brezel/api@1.0.0 Since 1.0.0, Brezel ships with a Laravel Reverb based Brotcast server integrated! Once you setup Supervisor, the server will be started automatically.

If you are not using the Laravel Reverb based Brotcast server, setup brotcast via Docker

Then create a docker-compose.yml file with the following content:

services:
brotcast:
image: registry.kiwis-and-brownies.de/kibro/brezel/brotcast-server:latest
ports:
- "8086:8086"
environment:
- SOKETI_PORT=8086
- SOKETI_DEFAULT_APP_ID=brotcast
- SOKETI_DEFAULT_APP_KEY=brotcast-pusher
- SOKETI_DEFAULT_APP_SECRET=<a alphanumerical secret>

Run docker compose up in the same directory.

Performance tweaks for a Reverb based Brotcast server

You might want to take a look at https://laravel.com/docs/11.x/reverb#production for some possible performance considerations.

Especially increasing the available ports / file descriptors as well as switching to a different runtime might be beneficial.

Configuration

Configure the brotcast environment in the .env file:

# Broadcasting settings
BROADCAST_DRIVER=reverb# either `pusher` or `reverb` Use `reverb` if you are using the Laravel Reverb based Brotcast server (Recommended)
# Where to run the Brotcast server itself if using reverb
BREZEL_BROTCAST_SERVER_HOST=127.0.0.1
BREZEL_BROTCAST_SERVER_PORT=8086
# How to connect to the Brotcast server. This url should point to a pusher-compatible websocket server.
# If you are using the `pusher` driver, this should point to a brezel/brotcast-server instance
# If using the `reverb` driver, this should point to the Brotcast server endpoint defined above
BREZEL_BROTCAST_HOST=ws.brezel.example.io
BREZEL_BROTCAST_PORT=443
# Common Brotcast settings
# When using the `pusher` driver, these should be your pusher credentials and match what is configured in the Brotcast server
BREZEL_BROTCAST_APP_ID=brezel
BREZEL_BROTCAST_KEY=brotcast-pusher
BREZEL_BROTCAST_SECRET=an-alphanumerical-secret
BREZEL_BROTCAST_SCHEME=https
# Only relevant when using the `pusher` driver
BREZEL_BROTCAST_APP_CLUSTER=mt1

Set up crons

If you want to use event/cron, you need to set up a cronjob. Very basically: this should trigger php bakery schedule in the directory of your instance every minute as the user that normally runs your brezel (when using nginx and following this setup it will be www-data). How you achieve this depends on you, especially on your local setup, but on a server it makes sense to use cron.

Make sure you are the brezel user and run

Terminal window
crontab -e

Now add the following line:

* * * * * cd /var/www/vhosts/api.brezel.example.io && php bakery schedule >> /dev/null 2>&1

Finally, verify that this was saved correctly by running crontab -l and checking if the line is there.

Set up the export service

If your brezel does stuff like creating pdfs or filling .docx templates, you need to set up the export service.

This is a container based environment that provides services like WKHtmlToPdf, Pandoc and more.

Installation instructions and a handy CLI can be found here: https://gitlab.kiwis-and-brownies.de/kibro/brezel/services/export/export-installer

Now you need to configure the export service in your .env file:

BREZEL_EXPORT_URL=http://127.0.0.1:5580

Install and configure supervisor

Supervisor handles the starting and stopping all the php-based services Brezel needs and ships with brezel/api. That includes brotcast (if you use Reverb), the queues and so on.

Installing supervisor

Switch to a user with sudo privileges and install supervisor:

Terminal window
sudo apt-get update
sudo apt-get install supervisor

Generate the supervisor configuration

Switch back to the brezel user and go into the instance directory.

Now copy supervisord.conf.example to supervisord.conf and adjust the socket names if desired.

Now run the following command to create the supervisor configuration:

Terminal window
php bakery make:supervisor

Now we register our supervisor programms with the supervisor daemon.

We want our supervisor programs to start on system boot.

For that, create the following systemd service file (/etc/systemd/system/supervisord-brezel.service) to start and manage our supervisor programms:

/etc/systemd/system/supervisord-brezel.service
[Unit]
Description=Run supervisord with the programs needed for the brezel instance on this server
Documentation=https://docs.brezel.io
After=network.target
[Service]
WorkingDirectory=/var/www/vhosts/api.brezel.example.io
ExecStart=/usr/bin/supervisord -c supervisord.conf -n
ExecStartPost=/bin/echo "[systemd] supervisord started at $(date)" >> storage/logs/supervisor.log
ExecReload=/usr/bin/supervisorctl -c supervisord.conf reload
ExecStop=/usr/bin/supervisorctl -c supervisord.conf stop all
ExecStopPost=/bin/echo "[systemd] supervisord stopped at $(date)" >> storage/logs/supervisor.log
KillMode=mixed
Restart=on-failure
RestartSec=42s
User=brezel
[Install]
WantedBy=multi-user.target

Now run the following commands to enable and start the supervisord service:

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable supervisord-brezel
sudo systemctl start supervisord-brezel

To check if all services are up and running, switch back to the brezel user, go into the instance directory and run:

Terminal window
supervisorctl status

Interacting with the supervisor service

If you want to restart the supervisor service, do so using the following command:

Terminal window
sudo systemctl restart supervisord-brezel

Stopping the service is done with:

Terminal window
sudo systemctl stop supervisord-brezel

Seeing the status of the services can be done with:

Terminal window
systemctl status supervisord-brezel

And starting as defined above:

Terminal window
sudo systemctl start supervisord-brezel