wayanjimmy
ENID

Building a Laravel Application Container with FrankenPHP

What is FrankenPHP? #

FrankenPHP is a PHP server that is actually a module of the Caddy web server. Since Caddy is written in Go, FrankenPHP enables a self-contained executable feature, where your Laravel application is wrapped into a single binary to streamline the deployment process.

Preparation #

Make sure your Laravel project is ready. In this post, I’m using this blog which is built with Laravel, with the note that the frontend assets in this blog project are built using Bun.

Creating a Multi-Stage Container File #

plaintext
FROM docker.io/oven/bun:1 AS asset-builder

COPY package.json bun.lockb /app/

WORKDIR /app

RUN bun install

COPY . .

RUN bun run build

FROM docker.io/serversideup/php:8.4-cli as vendor

COPY --chown=www-data:www-data . /var/www/html

RUN composer install --no-interaction --optimize-autoloader --ignore-platform-reqs --no-dev

FROM docker.io/dunglas/frankenphp:static-builder-musl-1.7.0 AS builder

ENV NO_COMPRESS=1

# Copy your app
WORKDIR /go/src/app/dist/app

COPY . .

# Remove the tests and other unneeded files to save space
# Alternatively, add these files to a .dockerignore file
RUN rm -Rf tests/
COPY --from=vendor /var/www/html /go/src/app/dist/app
COPY --from=asset-builder /app/public/build /go/src/app/dist/app/public/build

# Build the static binary, be sure to select only the PHP extensions you want
WORKDIR /go/src/app/

RUN EMBED=dist/app/ ./build-static.sh

FROM gcr.io/distroless/static-debian12

ARG build=dev

ENV BUILD $build

WORKDIR /app

COPY --from=builder /go/src/app/dist/frankenphp-linux-x86_64 blog

ENTRYPOINT ["/app/blog", "php-server"]

Explanation of the Multi-Stage Build #

Stage 1: Asset Builder #

This stage uses Bun to build frontend assets (CSS, JavaScript):

Stage 2: PHP Dependencies #

This stage contains Composer dependency installation, using the base image from serversideup:

Stage 3: Static Binary Build #

Creating a static binary with FrankenPHP:

Stage 4: Final Runtime #

The final stage that produces the production image

Building the Docker Image #

Run the following command to build the image:

bash
podman build --format=docker \
    -t wayanjimmy/blog-10:latest .

Running the Container #

After the build is complete, make sure the container runs without issues:

bash
podman container run -p 8080:80 \
    --env-file=./.env --rm wayanjimmy/blog-10:latest

Extracting the Static Binary #

If you want to get just the static binary:

bash
podman create --name temp-container wayanjimmy/blog-10:latest
podman cp temp-container:/app/blog ./blog-binary
podman rm temp-container

Now the blog-binary file can be run directly:

bash
chmod +x blog-binary
./blog-binary php-server

Conclusion #

By using FrankenPHP and multi-stage Container builds, we can transform a Laravel application into a self-contained single binary. This will streamline the deployment process by eliminating several components that need to be run, such as nginx/apache and php-fpm.

Although the binary size is quite large (around 199MB), the deployment ease gained is, in my opinion, worth it. This approach is very suitable for simplifying deployed components, and like this blog, I deploy it to a Fly.io server.

Subscribe