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

Available since: brezel/api@1.0.0

  • Root access
  • Ubuntu or Debian
  • Nginx
  • GitLab repository for your Brezel instance

0. Install Nginx, PHP, PHP extensions other needed dependencies, Imagick, CertBot and MySQL/MariaDB

  1. Nginx, PHP, PHP extensions, and other necessary dependencies
Terminal window
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install nginx php8.3-cli php8.3-fpm php8.3-curl php8.3-xml php8.3-opcache php8.3-mysql php8.3-zip php8.3-exif php8.3-bcmath php8.3-sockets php8.3-gmp php8.3-imap php8.3-intl php8.3-gd unzip ghostscript
  1. Imagick
Terminal window
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install php8.3-imagick
  1. Certbot for SSL certificates See https://certbot.eff.org/instructions?ws=nginx 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

  1. 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 probably don’t want anything related to changing the root users passwort, but do want to remove the test database and disallow remote root login.

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

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

1. 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

2. 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

Secure the SPA domain

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

3. Add the API domain

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;
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.3-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

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.

Configure your php-fpm settings in /etc/php/8.3/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.

Finally, add the following values to your /etc/php/8.3/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

Restart Nginx:

Terminal window
sudo systemctl restart nginx

Secure the API domain

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

4. Configure Brotcast

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

Installation

Run via Laravel Reverb New

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. In this case you only need to configure the nginx proxy

Run it via docker compose

Make sure you have docker installed.

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.

Add the domain

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

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream brotcast {
server 127.0.0.1:8086; # When hosted via docker compose or laravel reverb
}
server {
listen 80;
listen [::]:80;
server_name ws.brezel.example.io;
location / {
proxy_pass http://brotcast;
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;
}
}

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

Secure the Brotcast domain

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

Performance tweaks

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.

5. Create a new user for Brezel

  1. Create the new user.
Terminal window
adduser brezel
  1. Add your new user to the www-data group to ensure the correct permissions for the Nginx web server.
Terminal window
usermod -aG www-data brezel
  1. Create the web root directory for the new user.
Terminal window
mkdir -p /var/www/vhosts/api.brezel.example.io

Set the correct permissions:

Terminal window
chown -R brezel:www-data /var/www/vhosts/api.brezel.example.io
chmod -R 775 /var/www/vhosts/api.brezel.example.io

6. Connect the server to GitLab

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.

Create SSH keys for GitLab

  1. Switch to the new user.
Terminal window
su - brezel
  1. Generate an RSA key pair:

Now generate the key.

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_rsa).

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_rsa.
Your public key has been saved in /home/brezel/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:hcsvjIXxM8ZS16X3IHIoGFwPSHr5Cjw35HOIZMEIW1c brezel@lvps5-35-243-342.dedicated.hosteurope.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]-----+

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_rsa.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
  1. 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 .

7. Set up Brezel

Install composer and do

Terminal window
mv composer.phar ~/bin/composer

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.

Update folder permissions for the storage directory:

Terminal window
chmod -R 775 storage

Next, configure your environment:

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 mariadb if you are running MariaDB):

Terminal window
systemctl restart mysql

For the database credentials:

Variant 1: Use root credentials

TENANCY_DATABASE="brezel"
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;

You don’t need to do that if you did it while installing MariaDB!

And set the following credentials:

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

Setting up the brotcast environment

Configure the brotcast environment in the .env file:

# Broadcasting settings
# This url should point to a brezel/brotcast-server instance
BROADCAST_DRIVER=pusher
BREZEL_BROTCAST_HOST=ws.brezel.example.io
BREZEL_BROTCAST_PORT=443
BREZEL_BROTCAST_APP_ID=brotcast
BREZEL_BROTCAST_KEY=brotcast-pusher
BREZEL_BROTCAST_SECRET=<the same alphanumerical secret as set in the docker-compose.yml from the setting up brotcast step>
BREZEL_BROTCAST_SCHEME=https
BREZEL_BROTCAST_APP_CLUSTER=mt1

8. Set up the pipeline

Follow the Pipeline guide.

Initialization

Ensure that you have an .env file in the root of your instance with the correct values. Copy the .env.example file and adjust the values.

Make sure to change APP_ENV to “production”!

If the database credentials were set up, you can initialize the instance with

Terminal window
php bakery init

Now, you can add your system

Terminal window
php bakery system create example

And fill it with your config:

Terminal window
php bakery apply
php bakery load

9. 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.

Terminal window
sudo chown -R brezel:www-data /var/www/vhosts/api.brezel.example.io
sudo chmod -R 775 /var/www/vhosts/api.brezel.example.io
sudo find /var/www/vhosts/api.brezel.example.io -type d -exec chmod g+s {} \;

10. Install and configure supervisor

For async workflows and other background jobs to work, we need to install supervisor.

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

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

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

Ensure you are still in the instance directory and run the following command to create the supervisor configuration:

Terminal window
php bakery make:supervisor

Now start supervisor in your instance directory with:

Terminal window
supervisord -c supervisord.conf

Check that all jobs are up and running:

Terminal window
supervisorctl status

12. 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/your.brezel.system && 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.