---
title: "Building a Laravel Application Container with FrankenPHP"
description: "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 wr..."
publishedAt: 2025-06-25
locale: en
urlSlug: laravel-frankenphp-dalam-container
isDraft: false
defaultLocale: en
---
[TOC]

## What is FrankenPHP?

[FrankenPHP](https://frankenphp.dev/) 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](https://gitlab.com/jimboylabs/blog-10) which is built with Laravel, with the note that the frontend assets in this blog project are built using [Bun](https://github.com/oven-sh/bun).

## Creating a Multi-Stage Container File

```Dockerfile
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):
- Install dependencies with `bun install`
- Build assets with `bun run build`
- The build results will be copied to the next stage

### Stage 2: PHP Dependencies

This stage contains Composer dependency installation, using the base image from [serversideup](https://github.com/serversideup/docker-php):
- Install Composer dependencies with production optimization
- The `--no-dev` flag to skip development dependencies
- The `--optimize-autoloader` flag for better performance

### Stage 3: Static Binary Build

Creating a static binary with FrankenPHP:
- Using the base image `dunglas/frankenphp:static-builder-musl-1.7.0`
- Combining source code, Composer dependencies, and frontend build assets
- Activating the `NO_COMPRESS` variable to avoid the binary compression process as this will increase the container image build time
- Running the `build-static.sh` script from FrankenPHP to compile into a single executable

### Stage 4: Final Runtime

The final stage that produces the production image
- Using the base image `distroless/static-debian12`
- Only contains the compiled static binary

## 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](https://fly.io/) server.
