Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
af3038f
update installation instructions to use php 8.5 and add apk alpine su…
henderkes Dec 25, 2025
164cd88
update readme for new apk packages
henderkes Dec 25, 2025
5af6b5c
openrc service, not systemd
henderkes Dec 25, 2025
4dcbd5e
add alpine package scripts thanks to @alexandre-daubois
henderkes Dec 25, 2025
b692de1
Merge branch 'main' into docs/apk-packages
henderkes Dec 25, 2025
dd99631
Merge branch 'main' into docs/apk-packages
henderkes Dec 30, 2025
0c0bd6a
only run frankenphp trust on initial install
henderkes Jan 2, 2026
ba7b156
add completion scripts to debian and rhel bash (alpine doesn't have c…
henderkes Jan 2, 2026
43c571b
Merge remote-tracking branch 'php/main' into docs/apk-packages
henderkes Jan 2, 2026
308394f
linter
henderkes Jan 2, 2026
2205431
linter
henderkes Jan 2, 2026
515fa56
safety check if caddy or something else currently uses port 2019
henderkes Jan 18, 2026
96974fb
check specifically for localhost or 0.0.0.0 - if someone binds on a d…
henderkes Jan 18, 2026
2a0ce16
make globbing linter happy
henderkes Jan 18, 2026
cf1bd0c
linter doesn't like semicolons either :/
henderkes Jan 18, 2026
0ea54f5
trust before starting service
henderkes Jan 20, 2026
4fc731e
create completion dir if it doesn't exist
henderkes Jan 21, 2026
c20a2f6
explicit working directory
henderkes Jan 21, 2026
2c54949
make openrc service executable
henderkes Jan 21, 2026
578bfa2
add cap_net_bind_service capabilties to frankenphp.openrc
henderkes Jan 21, 2026
2f8217e
make debian and rhel service the same
henderkes Jan 21, 2026
011a9c7
better description
henderkes Jan 21, 2026
e31dfa2
same description for all
henderkes Jan 21, 2026
a5c5977
different bash completion dir
henderkes Jan 22, 2026
d7dabee
different bash completion dir on rhel too
henderkes Jan 22, 2026
730bd45
make linter happy
henderkes Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ sudo pie-zts install asgrim/example-pie-extension
Our maintainers offer deb packages for all systems using `apt`. To install, run:

```console
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
VERSION=85 # 82-85 available
sudo curl https://pkg.henderkes.com/api/packages/${VERSION}/debian/repository.key -o /etc/apt/keyrings/static-php${VERSION}.asc
echo "deb [signed-by=/etc/apt/keyrings/static-php${VERSION}.asc] https://pkg.henderkes.com/api/packages/${VERSION}/debian php-zts main" | sudo tee -a /etc/apt/sources.list.d/static-php${VERSION}.list
sudo apt update
sudo apt install frankenphp
```
Expand All @@ -75,6 +76,28 @@ sudo apt install pie-zts
sudo pie-zts install asgrim/example-pie-extension
```

### apk Packages

Our maintainers offer apk packages for all systems using `apk`. To install, run:

```console
VERSION=85 # 82-85 available
echo "https://pkg.henderkes.com/api/packages/${VERSION}/alpine/main/php-zts" | sudo tee -a /etc/apk/repositories
KEYFILE=$(curl -sJOw '%{filename_effective}' https://pkg.henderkes.com/api/packages/${VERSION}/alpine/key)
sudo mv ${KEYFILE} /etc/apk/keys/ &&
sudo apk update &&
sudo apk add frankenphp
```

**Installing extensions:** `sudo apk add php-zts-<extension>`

For extensions not available by default, use [PIE](https://github.com/php/pie):

```console
sudo apk add pie-zts
sudo pie-zts install asgrim/example-pie-extension
```

### Homebrew

FrankenPHP is also available as a [Homebrew](https://brew.sh) package for macOS and Linux.
Expand Down
6 changes: 3 additions & 3 deletions docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ However, it is possible to substantially improve performance using an appropriat

By default, FrankenPHP starts 2 times more threads and workers (in worker mode) than the available numbers of CPU.

The appropriate values depend heavily on how your application is written, what it does and your hardware.
The appropriate values depend heavily on how your application is written, what it does, and your hardware.
We strongly recommend changing these values. For best system stability, it is recommended to have `num_threads` x `memory_limit` < `available_memory`.

To find the right values, it's best to run load tests simulating real traffic.
Expand Down Expand Up @@ -43,7 +43,7 @@ Also, [some bugs only happen when using musl](https://github.com/php/php-src/iss

In production environments, we recommend using FrankenPHP linked against glibc, compiled with an appropriate optimization level.

This can be achieved by using the Debian Docker images, using our maintainers [.deb](https://debs.henderkes.com) or [.rpm](https://rpms.henderkes.com) packages, or by [compiling FrankenPHP from sources](compile.md).
This can be achieved by using the Debian Docker images, using [our maintainers .deb, .rpm, or .apk packages](https://pkgs.henderkes.com), or by [compiling FrankenPHP from sources](compile.md).

## Go Runtime Configuration

Expand Down Expand Up @@ -146,7 +146,7 @@ All usual PHP-related performance optimizations apply with FrankenPHP.

In particular:

- check that [OPcache](https://www.php.net/manual/en/book.opcache.php) is installed, enabled and properly configured
- check that [OPcache](https://www.php.net/manual/en/book.opcache.php) is installed, enabled, and properly configured
- enable [Composer autoloader optimizations](https://getcomposer.org/doc/articles/autoloader-optimization.md)
- ensure that the `realpath` cache is big enough for the needs of your application
- use [preloading](https://www.php.net/manual/en/opcache.preloading.php)
Expand Down
37 changes: 33 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ Linux*)
echo "❗ Enter your password to grant sudo powers for package installation"
${SUDO} -v || true
fi
${SUDO} dnf -y install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
${SUDO} dnf -y module enable php-zts:static-8.4 || true
${SUDO} dnf -y install https://rpm.henderkes.com/static-php-1-1.noarch.rpm
${SUDO} dnf -y module enable php-zts:static-8.5 || true
${SUDO} dnf -y install frankenphp
echo
echo "🥳 FrankenPHP installed to ${italic}/usr/bin/frankenphp${normal} successfully."
Expand All @@ -56,8 +56,8 @@ Linux*)
echo "❗ Enter your password to grant sudo powers for package installation"
${SUDO} -v || true
fi
${SUDO} sh -c 'curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg'
${SUDO} sh -c 'echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" > /etc/apt/sources.list.d/static-php.list'
${SUDO} sh -c 'curl -fsSL https://pkg.henderkes.com/api/packages/85/debian/repository.key -o /etc/apt/keyrings/static-php85.asc'
${SUDO} sh -c 'echo "deb [signed-by=/etc/apt/keyrings/static-php85.asc] https://pkg.henderkes.com/api/packages/85/debian php-zts main" | sudo tee -a /etc/apt/sources.list.d/static-php85.list'
if command -v apt >/dev/null 2>&1; then
${SUDO} apt update
${SUDO} apt -y install frankenphp
Expand All @@ -73,6 +73,35 @@ Linux*)
echo "⭐ If you like FrankenPHP, please give it a star on GitHub: ${italic}https://github.com/php/frankenphp${normal}"
exit 0
fi

if command -v apk >/dev/null 2>&1; then
echo "📦 Detected apk. Installing FrankenPHP from APK repository..."
if [ -n "${SUDO}" ]; then
echo "❗ Enter your password to grant sudo powers for package installation"
${SUDO} -v || true
fi

KEY_URL="https://pkg.henderkes.com/api/packages/85/alpine/key"
${SUDO} sh -c "cd /etc/apk/keys && curl -JOsS \"$KEY_URL\" 2>/dev/null || true"

REPO_URL="https://pkg.henderkes.com/api/packages/85/alpine/main/php-zts"
if grep -q "$REPO_URL" /etc/apk/repositories 2>/dev/null; then
echo "Repository already exists in /etc/apk/repositories"
else
${SUDO} sh -c "echo \"$REPO_URL\" >> /etc/apk/repositories"
${SUDO} apk update
echo "Repository added to /etc/apk/repositories"
fi

${SUDO} apk add frankenphp
echo
echo "🥳 FrankenPHP installed to ${italic}/usr/bin/frankenphp${normal} successfully."
echo "❗ The OpenRC service uses the Caddyfile in ${italic}/etc/frankenphp/Caddyfile${normal}"
echo "❗ Your php.ini is found in ${italic}/etc/php-zts/php.ini${normal}"
echo
echo "⭐ If you like FrankenPHP, please give it a star on GitHub: ${italic}https://github.com/php/frankenphp${normal}"
exit 0
fi
fi

case ${ARCH} in
Expand Down
29 changes: 29 additions & 0 deletions package/alpine/frankenphp.openrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/sbin/openrc-run

name="FrankenPHP"
description="The modern PHP app server"

command="/usr/bin/frankenphp"
command_args="run --environ --config /etc/frankenphp/Caddyfile"
command_user="frankenphp:frankenphp"
command_background="yes"
capabilities="^cap_net_bind_service"
pidfile="/run/frankenphp/frankenphp.pid"
start_stop_daemon_args="--chdir /var/lib/frankenphp"

depend() {
need net
after firewall
}

start_pre() {
checkpath --directory --owner frankenphp:frankenphp --mode 0755 /run/frankenphp

$command validate --config /etc/frankenphp/Caddyfile
}

reload() {
ebegin "Reloading $name configuration"
$command reload --config /etc/frankenphp/Caddyfile --force
eend $?
}
13 changes: 13 additions & 0 deletions package/alpine/post-deinstall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh

if getent passwd frankenphp >/dev/null; then
deluser frankenphp
fi

if getent group frankenphp >/dev/null; then
delgroup frankenphp
fi

rmdir /var/lib/frankenphp 2>/dev/null || true

exit 0
43 changes: 43 additions & 0 deletions package/alpine/post-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/sh

if ! getent group frankenphp >/dev/null; then
addgroup -S frankenphp
fi

if ! getent passwd frankenphp >/dev/null; then
adduser -S -h /var/lib/frankenphp -s /sbin/nologin -G frankenphp -g "FrankenPHP web server" frankenphp
fi

chown -R frankenphp:frankenphp /var/lib/frankenphp
chmod 755 /var/lib/frankenphp

# allow binding to privileged ports
if command -v setcap >/dev/null 2>&1; then
setcap cap_net_bind_service=+ep /usr/bin/frankenphp || true
fi

# check if 0.0.0.0:2019 or 127.0.0.1:2019 are in use
port_in_use() {
port_hex=$(printf '%04X' "$1")
grep -qE "(00000000|0100007F):${port_hex}" /proc/net/tcp 2>/dev/null
}

# trust frankenphp certificates if the admin api can start
if [ -x /usr/bin/frankenphp ]; then
if ! port_in_use 2019; then
HOME=/var/lib/frankenphp /usr/bin/frankenphp run >/dev/null 2>&1 &
FRANKENPHP_PID=$!
sleep 2
HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || true
kill -TERM $FRANKENPHP_PID 2>/dev/null || true

chown -R frankenphp:frankenphp /var/lib/frankenphp
fi
fi

if command -v rc-update >/dev/null 2>&1; then
rc-update add frankenphp default
rc-service frankenphp start
fi

exit 0
11 changes: 11 additions & 0 deletions package/alpine/pre-deinstall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

if command -v rc-service >/dev/null 2>&1; then
rc-service frankenphp stop || true
fi

if command -v rc-update >/dev/null 2>&1; then
rc-update del frankenphp default || true
fi

exit 0
7 changes: 5 additions & 2 deletions package/debian/frankenphp.service
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Unit]
Description=FrankenPHP
Description=FrankenPHP - The modern PHP app server
Documentation=https://frankenphp.dev/docs/
After=network.target network-online.target
Requires=network-online.target
Expand All @@ -8,12 +8,15 @@ Requires=network-online.target
Type=notify
User=frankenphp
Group=frankenphp
ExecStartPre=/usr/bin/frankenphp validate --config /etc/frankenphp/Caddyfile
ExecStart=/usr/bin/frankenphp run --environ --config /etc/frankenphp/Caddyfile
ExecReload=/usr/bin/frankenphp reload --config /etc/frankenphp/Caddyfile --force
ExecReload=/usr/bin/frankenphp reload --config /etc/frankenphp/Caddyfile
WorkingDirectory=/var/lib/frankenphp
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectHome=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE

Expand Down
23 changes: 16 additions & 7 deletions package/debian/postinst.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,29 @@ if [ "$1" = "configure" ]; then
usermod -aG www-data frankenphp
fi

# trust frankenphp certificates before starting the systemd service
if [ -z "$2" ] && [ -x /usr/bin/frankenphp ]; then
HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null &
FRANKENPHP_PID=$!
sleep 2
HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || true
kill "$FRANKENPHP_PID" || true
wait "$FRANKENPHP_PID" 2>/dev/null || true

chown -R frankenphp:frankenphp /var/lib/frankenphp
fi

# Handle cases where package was installed and then purged;
# user and group will still exist but with no home dir
if [ ! -d /var/lib/frankenphp ]; then
mkdir -p /var/lib/frankenphp
chown frankenphp:frankenphp /var/lib/frankenphp
chown -R frankenphp:frankenphp /var/lib/frankenphp
fi

# Add log directory with correct permissions
if [ ! -d /var/log/frankenphp ]; then
mkdir -p /var/log/frankenphp
chown frankenphp:frankenphp /var/log/frankenphp
chown -R frankenphp:frankenphp /var/log/frankenphp
fi
fi

Expand Down Expand Up @@ -63,9 +75,6 @@ if command -v setcap >/dev/null 2>&1; then
fi

if [ -x /usr/bin/frankenphp ]; then
HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null &
FRANKENPHP_PID=$!
HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || true
kill "$FRANKENPHP_PID" || true
wait "$FRANKENPHP_PID" 2>/dev/null || true
mkdir -p /usr/share/bash-completion/completions/
/usr/bin/frankenphp completion bash | sed 's/caddy/frankenphp/g' >/usr/share/bash-completion/completions/frankenphp
fi
6 changes: 4 additions & 2 deletions package/rhel/frankenphp.service
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[Unit]
Description=FrankenPHP server
After=network.target
Description=FrankenPHP - The modern PHP app server
Documentation=https://frankenphp.dev/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
Expand Down
27 changes: 22 additions & 5 deletions package/rhel/postinstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,27 @@ if command -v setcap >/dev/null 2>&1; then
setcap cap_net_bind_service=+ep /usr/bin/frankenphp || :
fi

# check if 0.0.0.0:2019 or 127.0.0.1:2019 are in use
port_in_use() {
port_hex=$(printf '%04X' "$1")
grep -qE "(00000000|0100007F):${port_hex}" /proc/net/tcp 2>/dev/null
}

# trust frankenphp certificates if the admin api can start
if [ "$1" -eq 1 ] && [ -x /usr/bin/frankenphp ]; then
if ! port_in_use 2019; then
HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null &
FRANKENPHP_PID=$!
sleep 2
HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || :
kill "$FRANKENPHP_PID" || :
wait "$FRANKENPHP_PID" 2>/dev/null || :

chown -R frankenphp:frankenphp /var/lib/frankenphp
fi
fi

if [ -x /usr/bin/frankenphp ]; then
HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null &
FRANKENPHP_PID=$!
HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || :
kill "$FRANKENPHP_PID" || :
wait "$FRANKENPHP_PID" 2>/dev/null || :
mkdir -p /usr/share/bash-completion/completions/
/usr/bin/frankenphp completion bash | sed 's/caddy/frankenphp/g' >/usr/share/bash-completion/completions/frankenphp
fi
Loading