Running firefly3 on alpine


Disclaimer: before starting be aware that I’m not a sysadmin nor I have a deep knowledge in security. This is me reporting the steps I did as a learning experiment, so take this tutorial as your own risk.

I have a pretty decent knowledge in container technology, I maintain several container on my local server for many applications. However I’ve decided to take a step back and learn a bit more how those applications are really deployed and kept without containers, and first candidate being Firefly1. I have it currently running on container but let’s install in a distribution.

For the distro of choice I’ll pick alpine, for its small footprint and the use of OpenRC (nothing against systemd though), and it will help me later to better understand how to properly setup an alpine image on container environment.

I don’t want to extend this tutorial to cover every single part, so for the next steps I’ll assume that you have a running instance of PostgreSQL and Alpine.

Dependencies

First we need to install all the necessary packages to get firefly running. Let’s go through them and check what they are used for.

apk add curl tar gzip

cURL is needed to download the source code from Github and tar gzip are for extracting the compressed code.

apk add composer

Composer is a dependency manager for PHP. It is required to download the dependencies of the project, as the source code from tar ball does have all its dependencies included.

Now we need to download the dependencies listed in the site2.

 Extra packages
 Install the following PHP modules:
    PHP BCMath Arbitrary Precision Mathematics
    PHP Internationalization extension
    PHP Curl
    PHP Zip
    PHP Sodium
    PHP GD
    PHP XML
    PHP MBString
    PHP whatever database you're gonna use.

And for those I could gather the following alpine packages:

apk add \
    php8 \
    php8-curl \
    php8-zip \
    php8-sodium \
    php8-gd \
    php8-xml \
    php8-mbstring \
    php8-bcmath \
    php8-pgsql

But that is not everything. I don’t know if I lack knowledge in the PHP stack but the application will later complain about some other missing dependencies. Those being:

apk add \
    php8-fileinfo \
    php8-intl \
    php8-session \
    php8-simplexml \
    php8-tokenizer \
    php8-xmlwriter \
    php8-dom \
    php8-pdo_pgsql \
    php8-shmop

A tip that may as well help you later. Some of those not listed packages are described in their docker repository3 and its base image4. It can also help with describing some other necessary steps.

As the next step we need to install the pieces of software that will actually run the project:

apk add nginx php8-fpm

Nginx will act as reverse proxy and php8-fpm will actually run the project. You can use lighttpd as well as some others.

Deploying the code

Now we have all necessary packages, lets download the project into on server, grab the latest release from Github, at the time of this writing is 5.7.9. Download into the /var/www/firefly. The folder location is kinda up you, I think nginx itself has another default folder for its sites, but I always use www folder to store the projects.

mkdir -p /var/www/firefly

Create the folder then download/extract the source code:

curl -SL https://github.com/firefly-iii/firefly-iii/archive/refs/tags/5.7.9.tar.gz | \
    tar zxC /var/www/firefly --strip-components 1

This piece of code was taken from the dockerfile5.

Now move to the /var/www/firefly and install its dependencies with composer:

cd /var/www/firefly
composer install --prefer-dist --no-dev --no-scripts

Configurations

Firefly

Firefly makes the process of setting up the connection strings and other configuration quite easy. We’ll only need to create an .env file with all the information needed. Fill the information according with your setup:

# /var/wwww/firefly/.env

DB_CONNECTION=pgsql
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=firefly
DB_USERNAME=admin
DB_PASSWORD=admin
APP_KEY=<RANDON_KEY>

To generate a random key just run:

head /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32 && echo

Once you have set it up we need to bootstrap the project. First we need to update the cached configuration.

php artisan config:cache

Second we need to migrate and seed the database:

php artisan firefly-iii:create-database
php artisan migrate:refresh --seed
php artisan firefly-iii:upgrade-database

If everything is setup properly the processes finish successfully.

Permission

Now comes the part where we should be careful. So far we (or at least I) have been setting up everything as root but that is not ideal. Usually we want to restrict as much as possible the permissions of processes, it should only see do what it meant to. So to minimize the area of effect of the process we will make it run as a user with almost no permission, and for purpose of running the php-fpm we will create a www-data user. Quite often that user is already created and if it is not, run the following command:

adduser www-data --disabled-password

Add --ingroup www-data if it complains if the groups exists. --disabled-password is given so we don’t allow login with password, because it is not meant to be logged with.

Once the user is created we need to change the which user the process runs on. By default it uses a nobody which is a user with no permission except those which every other user has. Update the user given in the /etc/php8/php-fpm.d/www.conf file.

From:

user = nobody
group = nobody

To:

user = www-data
group = www-data

If the php-fpm8 is running restart it:

rc-service php-fpm8 restart

At last we need to recursively update the permission of www folder because probably it is owned by root.

chown -R www-data:www-data /var/www/

Nginx

We will need to edit the nginx config file to find and run the project. Add the following server inside of /etc/nginx/http.d/, by default nginx will read all .conf inside of that folder. Just like the www folder this is more of a personal choice, you have some room to choose where you want to store the config file.

# /etc/nginx/http.d/firefly.conf

server {
    listen 8080;
    server_name localhost;


    root /var/www/firefly/public;

    location ~ \.php$ {
        try_files $uri $uri/ =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        include fastcgi.conf;
    }

    location / {
      try_files $uri /index.php$is_args$args;
    }
}

This will set up the process in the port 8080. It is just an exemple, adapt it to your needs.

Services

Now that we have everything set up we can start the service to serve firefly:

rc-service php-fpm8 start
rc-service nginx start

http://localhot:8080/ (or your server’s hostname) should be up and running.

And to make autostart:

rc-update add php-fpm8 default
rc-update add nginx default

Debugging

In case of error you can add debugging setting to your env file so it will nicely return the error.

# /var/wwww/firefly/.env
APP_DEBUG=true
APP_LOG_LEVEL=debug

Bonus config with socket

Another thing to look at is where php-fpm is running the service. I think by default on alpine it runs on http://127.0.0.1:9000 but it can also be running on a socket, check the www.conf file for the listen property:

Config for http

listen = 127.0.0.1:9000

Config for socket

listen = /run/php-fpm8/fpm.sock

If you want you can set it up to run on socket. You will need to change two things. First, update the www.conf file to run the process on a socket, and to change the owner of the socket file. This is important so later nginx is capable of reading/writing the file. On the /etc/php8/php-fpm.d/www.conf update it:

listen = /run/php-fpm8/fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

Second, change the nginx to connect to socket instead of an tcp connection, update the following property:

fastcgi_pass unix:/run/php-fpm8/fpm.sock;