Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: create a static build of FrankenPHP #198

Merged
merged 24 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
46 changes: 46 additions & 0 deletions .github/workflows/static.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Build binary releases
on:
pull_request:
branches:
- main
push:
branches:
- main
tags:
- v*
workflow_dispatch:
inputs: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
version: latest

- name: Build
id: build
uses: docker/bake-action@v3
with:
pull: true
load: true
targets: static-builder
set: |
*.cache-from=type=gha,scope=${{github.ref}}-static-builder
*.cache-from=type=gha,scope=refs/heads/main-static-builder
*.cache-to=type=gha,scope=${{github.ref}}-static-builder
env:
VERSION: ${{github.ref_name}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Copy binary
run: docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/caddy/frankenphp/frankenphp frankenphp ; docker rm static-builder

- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: frankenphp-dev
path: frankenphp
19 changes: 18 additions & 1 deletion docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ variable "VERSION" {
default = "dev"
}

variable "GO_VERSION" {
default = "1.21"
}

variable "SHA" {}

variable "LATEST" {
Expand Down Expand Up @@ -59,7 +63,7 @@ target "default" {
}
contexts = {
php-base = "docker-image://php:${php-version}-zts-${os}"
golang-base = "docker-image://golang:1.21-${os}"
golang-base = "docker-image://golang:${GO_VERSION}-${os}"
}
dockerfile = os == "alpine" ? "alpine.Dockerfile" : "Dockerfile"
context = "./"
Expand Down Expand Up @@ -91,3 +95,16 @@ target "default" {
FRANKENPHP_VERSION = VERSION
}
}

target "static-builder" {
contexts = {
golang-base = "docker-image://golang:${GO_VERSION}-alpine"
}
dockerfile = "static-builder.Dockerfile"
context = "./"
tags = ["${IMAGE_NAME}:static-builder"]
args = {
FRANKENPHP_VERSION = VERSION
}
secret = ["id=github-token,env=GITHUB_TOKEN"]
}
5 changes: 5 additions & 0 deletions docs/compile.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Compile From Sources

This document explain how to create a FrankenPHP build that will load PHP as a dymanic library.
This is the recommended method.

Alternatively, [creating static builds](static.md) is also possible.

## Install PHP

FrankenPHP is compatible with the PHP 8.2 and superior.
Expand Down
63 changes: 63 additions & 0 deletions docs/static.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Create a Static Build

Instead of using a local installation of the PHP library,
it's possible to create a static build of FrankenPHP thanks to the great [static-php-cli project](https://github.com/crazywhalecc/static-php-cli) (despite its name, this project support all SAPIs, not only CLI).

With this method, a single, portable, binary will contain the PHP interpreter, the Caddy web server and FrankenPHP!

## Linux

We provide a Docker image to build a Linux static binary:

```console
docker buildx bake --load static-builder
docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/caddy/frankenphp/frankenphp frankenphp ; docker rm static-builder
```

The resulting static binary is named `frankenphp` and is available in the current directory.

If you want to build the static binary without Docker, take a look to the `static-builder.Dockerfile` file.

### Custom Extensions

By default, most popular PHP extensions are compiled.

To reduce the size of the binary and to reduce the attack surface, you can choose the list of extensions to build using the `PHP_EXTENSIONS` Docker ARG.

For instance, run the following command to only build the `opcache` extension:

```console
docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache static-builder
dunglas marked this conversation as resolved.
Show resolved Hide resolved
# ...
```

### GitHub Token

If you hit the GitHub API rate limit, set a GitHub Personal Access Token in an environment variable named `GITHUB_TOKEN`:

```console
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder
# ...
```

## macOS

Note: only a very limited subset of extensions are currently available for static builds on macOS
because of a weird linking issue.

Run the following command to create a static binary for macOS:

```console
git clone --depth=1 https://github.com/dunglas/static-php-cli.git --branch=feat/embed
cd static-php-cli
composer install --no-dev -a
./bin/spc doctor
./bin/spc fetch --with-php=8.2 -A
./bin/spc build --enable-zts --build-embed --debug "opcache"
export CGO_CFLAGS="$(./buildroot/bin/php-config --includes | sed s#-I/#-I$PWD/buildroot/#g)"
export CGO_LDFLAGS="-L$PWD/buildroot/lib $(./buildroot/bin/php-config --ldflags) $(./buildroot/bin/php-config --libs)"

git clone --depth=1 https://github.com/dunglas/frankenphp.git
cd frankenphp/caddy/frankenphp
go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie"
```
11 changes: 8 additions & 3 deletions frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ package frankenphp
// Use PHP includes corresponding to your PHP installation by running:
//
// export CGO_CFLAGS=$(php-config --includes)
// export CGO_LDFLAGS=$(php-config --ldflags --libs)
//
// We also set these flags for hardening: https://github.com/docker-library/php/blob/master/8.2/bookworm/zts/Dockerfile#L57-L59

// #cgo pkg-config: libxml-2.0 sqlite3
// #cgo CFLAGS: -Wall -Werror
// #cgo darwin pkg-config: libxml-2.0 sqlite3
// #cgo CFLAGS: -Wall -Werror -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
// #cgo CFLAGS: -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -I/usr/local/include/php/ext/date/lib
// #cgo linux CFLAGS: -D_GNU_SOURCE
// #cgo CPPFLAGS: -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
// #cgo darwin LDFLAGS: -L/opt/homebrew/opt/libiconv/lib -liconv
// #cgo LDFLAGS: -L/usr/local/lib -L/usr/lib -lphp -lresolv -ldl -lm -lutil
// #cgo linux LDFLAGS: -Wl,-O1
// #cgo LDFLAGS: -pie -L/usr/local/lib -L/usr/lib -lphp -lresolv -ldl -lm -lutil
// #include <stdlib.h>
// #include <stdint.h>
// #include <php_variables.h>
Expand Down
69 changes: 69 additions & 0 deletions static-builder.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# syntax=docker/dockerfile:1
FROM golang-base

ARG FRANKENPHP_VERSION='dev'
ARG PHP_VERSION='8.2'
ARG PHP_EXTENSIONS='bcmath,calendar,ctype,curl,dba,dom,exif,filter,fileinfo,gd,iconv,mbstring,mbregex,mysqli,mysqlnd,openssl,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,readline,redis,session,simplexml,sockets,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib,apcu'

RUN apk update; \
apk add --no-cache \
autoconf \
automake \
bash \
binutils \
bison \
build-base \
cmake \
composer \
curl \
file \
flex \
g++ \
gcc \
git \
jq \
libgcc \
libstdc++ \
linux-headers \
m4 \
make \
php81 \
php81-common \
php81-pcntl \
php81-phar \
php81-posix \
php81-tokenizer \
php81-xml \
pkgconfig \
wget \
xz

WORKDIR /static-php-cli

RUN git clone --depth=1 https://github.com/dunglas/static-php-cli.git --branch=feat/embed . && \
composer install --no-cache --no-dev --classmap-authoritative

RUN --mount=type=secret,id=github-token GITHUB_TOKEN=$(cat /run/secrets/github-token) ./bin/spc download --with-php=$PHP_VERSION --all

RUN ./bin/spc build --build-embed --enable-zts --debug "$PHP_EXTENSIONS"

ENV PATH="/static-php-cli/buildroot/bin:/static-php-cli/buildroot/usr/bin:$PATH"

WORKDIR /go/src/app

COPY go.mod go.sum ./
RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get

RUN mkdir caddy && cd caddy
COPY caddy/go.mod caddy/go.sum ./caddy/

RUN cd caddy && go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get

COPY *.* ./
COPY caddy caddy
COPY C-Thread-Pool C-Thread-Pool

RUN cd caddy/frankenphp && \
CGO_CFLAGS="$(/static-php-cli/buildroot/bin/php-config --includes | sed s#-I/#-I/static-php-cli/buildroot/#g)" \
CGO_LDFLAGS="-L/static-php-cli/buildroot/lib $(/static-php-cli/buildroot/bin/php-config --ldflags) $(/static-php-cli/buildroot/bin/php-config --libs | sed -e 's/-lgcc_s//g')" \
go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie -s -w -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION Caddy'"