Skip to main content
Version: v4

Getting Started

To make installing PILOS easy, we provide a Docker image. The application will be installed with some default settings, but you can customize it later via the UI, config files and overriding some files.

Requirements

  • fully qualified hostname
  • valid SSL certificate (HTTPS)
  • reverse proxy, e.g. apache or nginx
  • Docker and Compose plugin

Docker Tags

We use Semantic Versioning for our releases and tag the images accordingly.

It is recommended to use the image with the latest major version e.g. v4.1.0 -> v4 to always get the latest features and bugfixes.

All Tags

Development Images

Additionally, we provide images for the latest commit on the develop branch (dev-develop) and the release branches, e.g. dev-v4.x.

Latest

Never use the latest tag for production, as upgrades between major versions is not supported. Always check the changelog and upgrade documentation before changing the major version!

Installing PILOS

Create a directory for the data and config of PILOS

mkdir pilos && cd pilos

To make the following steps independent to docker image changes, we define an environment variable. Adjust the docker image tag according to the version you want to use (see Docker Tags section).

export IMAGE="pilos/pilos:latest"

Next we need the docker-compose.yml and .env files. They can be copied from the image to your host.

docker run --rm $IMAGE cat ./.env.example > .env
docker run --rm $IMAGE cat ./docker-compose.yml > docker-compose.yml
sed -i "s|CONTAINER_IMAGE=.*|CONTAINER_IMAGE=$IMAGE|g" .env

Configuring PILOS

In the .env file all application settings can be found, some of them can be changed with the UI.

App-Key

We need to set the APP_KEY option in the .env file, as this is used to encrypt sessions, cookies, urls, etc. Using the following command a secure key is generated:

docker run --rm  --entrypoint pilos-cli $IMAGE key:generate --show

Copy the output and edit the APP_KEY option in the .env file.

Example: APP_KEY=base64:WJKM8YsGNutfcc3+2q/xiWE9Sus8GcbNVvTKFgdYgPw=

Host

You also need to edit the APP_URL option in the .env file to match the domain from which the application will be accessible from.

Example: APP_URL=https://pilos.example.com

Database

The default docker compose setup contains a MariaDB.

To create a secure default database password, run the following command:

openssl rand -hex 24

Copy the output and edit the DB_PASSWORD option in the .env file.

Example: DB_PASSWORD=44cefe1bc30ddf6bd2013a97f58353acb5e9000e7ec3bb90

Using PostgreSQL

You can also use PostgreSQL as an alternative database since PILOS v2.

Please note: We do NOT support migrating from v1.

To run a local PostgreSQL using docker compose, you have to adjust the docker-compose.yml file:

db:
image: postgres:14.6-alpine3.17
container_name: postgres
restart: unless-stopped
volumes:
- ./db:/var/lib/postgresql/data
environment:
POSTGRES_USER: "${DB_USERNAME}"
POSTGRES_PASSWORD: "${DB_PASSWORD}"
POSTGRES_DB: "${DB_DATABASE}"
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
retries: 3
timeout: 5s

To use a different database adjust the .env file:

# Database config
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432

Webserver

PILOS has a build in nginx webserver. However, it is highly recommended to not expose the container port to the public. You will need to set up a reverse proxy that routes the traffic to this application (default: 127.0.0.1:5000)

location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
}

Rate limiting

PILOS has a build in rate limiting, but you can also set some additional rate limiting on the webserver level. This example config allows 5 requests/sec. The configuration allows bursts of up to 20 requests, the first 10 of which are processed without delay. A delay is added after 10 excessive requests to enforce the 5 r/s limit. After 20 excessive requests, any further requests are rejected. For more details have a look at the nginx documentation on rate limting.

limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
location / {
limit_req zone=api burst=20 delay=10;
limit_req_log_level warn;
limit_req_status 429;

proxy_pass http://127.0.0.1:5000;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
}

Apache

ProxyPreserveHost On

ProxyPass "/" "http://127.0.0.1:5000/" nocanon
ProxyPassReverse "/" "http://127.0.0.1:5000/"

RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"

You may need to adjust the X-Forwarded-Proto and X-Forwarded-Port settings, depending on your environment.

Trusted proxies

By default, the application uses the requesting IP for logging and rate limiting. It also uses the requests host, port and protocol to generate URLs and links.

By running a reverse proxy in front of the application, the requesting IP will be the IP of the reverse proxy. As the ssl termination is done on the reverse proxy, the application will also not know if the request was made via https or http and on which port. Therefore, reverse proxies need to forward this information in a header (e.g. X-Forwarded-For). PILOS will use the following headers if the request comes from a trusted proxy and use them instead of the requests IP address, host, port and protocol:

  • X-Forwarded-For (Real client IP)
  • X-Forwarded-Host (Public hostname of the application)
  • X-Forwarded-Port (Public port of the application)
  • X-Forwarded-Proto (Public protocol of the application)

Warning Make sure all these headers are set by the reverse proxy, otherwise an attacker might be able to pass fake headers trough a trusted proxy to the application.

To prevent attackers from sending fake headers, the application needs to know which proxies are trustworthy.

Reverse proxy on the same host (Default)

If your reverse proxy is running on the same host as your application you should not expose the application port to the public (default 127.0.0.1:5000). As the traffic is going through the reverse proxy via the docker bridge network to the container the requesting IP will be the IP of the reverse proxy inside the docker network. This local IP cannot be predicted and therefore the application needs to trust all proxies. This will allow the application to use the headers from the reverse proxy.

TRUSTED_PROXIES=*

Reverse proxy on a different host

If your reverse proxy is on a different host in your local network, and you expose the application port, the requesting IP will be the IP of the reverse proxy. The application can then validate the IP address of the reverse proxy and trust it. This should prevent other users in you local network from spoofing the IP address by sending requests with a fake X-Forwarded-For header.

You can specify the IP address of the reverse proxy or a subnet of trusted IP addresses:

TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8

If you don't know the IP address of the reverse proxy you also can use the wildcard * to trust all proxies. Warning: Setting TRUSTED_PROXIES=* can pose a security risk, as it allows attackers to spoof their true IP address and other information. You therefore need to make sure only traffic from the reverse proxy can reach the application, e.g. by using a firewall.

Warning: If your reverse proxy is on a different host and therefore the ssl termination is done on the reverse proxy, all traffic between the reverse proxy and the application is unencrypted.

You can learn more on the topic in the Laravel documentation and Symfony documentation.

Starting

To start the application and database run:

docker compose up -d

You can monitor the startup with:

docker compose logs -f

Notice: If you modify the .env file you need to restart the container.

Creating a superuser

The first superuser can be created by running the following command:

docker compose exec app pilos-cli users:create:superuser

Logging

By default all nginx and php-fpm errors are written to stderr and shown in the docker logs. The application messages and errors are also written to stderr.

File based logging

To use a more persistent logging exclusive for the application, change the LOG_CHANNEL in the .env file to stack (a single file) or daily (log rotation) and mount the directory storage/logs to the host by adding the following line to the docker-compose.yml file:

x-docker-pilos-common: &pilos-common
env_file: .env
volumes:
- "./storage/logs:/var/www/html/storage/logs"

Monitoring

PILOS comes with a user interface for monitoring the application, server and queue workers. Administrators can access the monitoring UI via the 'System Monitoring' menu item. The php-fpm status page is available from container port 81 and the url /status.

You can forward the port to the host by adding the following line to the docker-compose.yml file for the app service:

ports:
- "127.0.0.1:9000:81"

Prometheus + Grafana

If you like to monitor php-fpm using prometheus and grafana, you can use the hipages/php-fpm_exporter:latest container. Add the following service to the docker-compose.yml file:

monitor:
image: hipages/php-fpm_exporter:latest
restart: always
ports:
- "127.0.0.1:9253:9253"
command: "--phpfpm.scrape-uri=tcp://app:81/status"

You can configure a reverse proxy to expose the container to the public, add password protection, configure prometheus and grafana to scrape the metrics and create dashboards.