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;