Server become slower or unresponsive with frankenphp worker mode and laravel octane
What happened?
Dockerfile
FROM dunglas/frankenphp
# Be sure to replace "your-domain-name.example.com" by your domain name
#ENV SERVER_NAME=api.********
ENV FRANKENPHP_CONFIG="worker /app/public/frankenphp-worker.php 8"
# If you want to disable HTTPS, use this value instead:
ENV SERVER_NAME=:80
# install packages
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libzip-dev \
libonig-dev \
libpq-dev \
nodejs \
npm \
supervisor \
# Clean up apt caches to reduce image size
&& rm -rf /var/lib/apt/lists/*
# add additional extensions here:
RUN install-php-extensions \
pdo_mysql \
gd \
intl \
pcntl \
zip \
opcache \
redis \
imagick \
xml \
simplexml \
iconv \
@composer
# Set working directory
WORKDIR /app
# copy php.ini
#COPY ./.docker/php/php.ini /usr/local/etc/php/conf.d/zz-production.ini
php.ini
memory_limit = 256M
upload_max_filesize = 128M
post_max_size = 100M
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
expose_php = Off
date.timezone = UTC
[opcache]
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
opcache.jit_buffer_size=100M
opcache.jit=tracing
/app/public/frankenphp-worker.php
<?php
// Set a default for the application base path and public path if they are missing...
$_SERVER['APP_BASE_PATH'] = $_ENV['APP_BASE_PATH'] ?? $_SERVER['APP_BASE_PATH'] ?? __DIR__.'/..';
$_SERVER['APP_PUBLIC_PATH'] = $_ENV['APP_PUBLIC_PATH'] ?? $_SERVER['APP_BASE_PATH'] ?? __DIR__;
require __DIR__.'/../vendor/laravel/octane/bin/frankenphp-worker.php';
Docker compose file
services:
app:
image: dazzle-backend:latest
build:
context: .
restart: always
volumes:
- ./:/app
- caddy_data:/data
- caddy_config:/config
depends_on:
- redis
- typesense
networks:
- dazzle_network
- traefik_proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.laravel.rule=Host(`#####`)"
- "traefik.http.routers.laravel.entrypoints=websecure"
- "traefik.http.routers.laravel.tls.certresolver=letsencrypt"
- "traefik.http.services.laravel.loadbalancer.server.port=80"
redis:
image: redis:alpine
hostname: redis
restart: always
command: redis-server /usr/local/etc/redis/redis.conf
volumes:
- ./.docker/redis/redis.conf:/usr/local/etc/redis/redis.conf
networks:
- dazzle_network
supervisor:
image: dazzle-backend:latest
entrypoint: ["./.docker/entrypoint.sh"]
restart: always
volumes:
- ./:/app
depends_on:
- redis
- typesense
- app
networks:
- dazzle_network
- traefik_proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.reverb.tls=true"
- "traefik.http.routers.reverb.entrypoints=websecure"
- "traefik.http.routers.reverb.rule=Host(`####`)"
- "traefik.http.services.reverb.loadbalancer.server.port=8080"
- "traefik.http.services.reverb.loadbalancer.server.scheme=http"
typesense:
image: typesense/typesense:27.1
restart: on-failure
environment:
TYPESENSE_API_KEY: ${TYPESENSE_API_KEY}
command: '--data-dir /data --api-key=typesense_dazzle'
volumes:
- typesense_data:/data
networks:
- dazzle_network
# Volumes needed for Caddy certificates and configuration
volumes:
caddy_data:
caddy_config:
typesense_data:
networks:
dazzle_network:
driver: bridge
traefik_proxy:
external: true
When worker is enabled then backend becomes slow or unresponsive after certain load. But after disabling worker mode everything is working fine. Whats wrong with this setup? And what is correct setup with laravel, docker and frankenphp (woker mode)?
Build Type
Docker (Debian Bookworm)
Worker Mode
Yes
Operating System
GNU/Linux
CPU Architecture
x86_64
PHP configuration
memory_limit = 256M
upload_max_filesize = 128M
post_max_size = 100M
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
expose_php = Off
date.timezone = UTC
[opcache]
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
opcache.jit_buffer_size=100M
opcache.jit=tracing
Relevant log output
How are you load-testing? How many CPU cores are you using? 8 workers might not be enough if you are doing IO.
Example minimal Caddyfile
{
# Caddy is the web server that serves the Laravel application using FrankenPHP
# https://caddyserver.com/docs/caddyfile
# https://frankenphp.dev/docs/config
admin localhost:2019
frankenphp {
max_threads {$MAX_THREADS:100} # max number of threads (conurrent requests)
max_wait_time 30s # max time to wait for threads
php_ini {
max_execution_time 30 # you can add more php_ini configuration here
}
}
}
{$SERVER_NAME::80} {
route {
encode zstd br gzip
php_server {
root /app/public
worker {
file "/app/frankenphp-worker.php" # laravel Octane entry file
num {$NUM_THREADS:8} # number of workers running from the start
{$WATCH} # set 'WATCH=watch' in the docker.compose environment to enable watching
match * # all requests go to the worker script
}
}
}
}
I'm using 4 core 8gb ram vps
When exactly does the backend become slow? After some time or after you spam a certain endpoint? What does the endpoint do?
What Laravel version are you using?
If you are you spamming an endpoint with sessions: What session driver are you using?
If you are spamming an endpoint with Db, what Db are you using?
Are you using php artisan octane:start somewhere or just the default entrypoint?
Your setup doesn't look too far off, you can try mounting the Caddyfile I provided above into /etc/frankenphp/Caddyfile and see if that makes a difference. Otherwise it's hard to say from just the info you provided. If the server is getting slower over time, it's also possible that there's a memory leak in Laravel or a library you are using.
Can you also provide a minimal reproducer of the endpoint that became unresponsive?