I ran into this while working on a project that needed to merge and compress PDFs on Laravel Cloud. I assumed I’d be able to install Ghostscript during the build somehow. Nope.
Laravel Cloud doesn’t ship system binaries like Ghostscript, and unlike FFmpeg there isn’t an official static arm64 build you can just grab. So I ended up compiling it, committing it to the repo, and shipping it with the app.
Why static?
Laravel Cloud runs on arm64 containers. There’s no apt-get install ghostscript during builds, no package manager at all, and build commands have a 15-minute limit.
Even if you could squeeze compilation into that window, rebuilding Ghostscript on every deploy sounded dumb. A static binary was the simplest option.
Ghostscript 10.04.0 built this way ends up at about 23MB.
Build a static gs binary
Create deploy/Dockerfile.gs-build:
FROM --platform=linux/arm64 debian:bookworm-slim AS build
RUN apt-get update && apt-get install -y build-essential wget
WORKDIR /work
RUN wget -q https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10040/ghostpdl-10.04.0.tar.gz \
&& tar -xzf ghostpdl-10.04.0.tar.gz
WORKDIR /work/ghostpdl-10.04.0
RUN ./configure --disable-dynamic --with-drivers=pdfwrite \
&& make -j$(nproc) LDFLAGS=-static gs
RUN cp bin/gs /gs && strip /gs
FROM scratch
COPY --from=build /gs /gs
A few flags that matter:
--disable-dynamic: no shared libs at runtimeLDFLAGS=-static: link everything staticallystrip /gs: strip debug symbols
Build and extract the binary:
docker build --platform linux/arm64 -f deploy/Dockerfile.gs-build -t gs-build .
docker create --name gs-extract gs-build
docker cp gs-extract:/gs ghostscript/gs
docker rm gs-extract
chmod +x ghostscript/gs
Add fonts
This was the part I missed at first: the binary alone is not enough. Ghostscript also needs the base fonts or some PDFs will render badly or fail.
Ghostscript needs the base URW fonts or some PDFs will choke. Easiest way is to grab them from the same GhostPDL tarball:
mkdir -p ghostscript/fonts
wget -q https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10040/ghostpdl-10.04.0.tar.gz
tar -xzf ghostpdl-10.04.0.tar.gz 'ghostpdl-10.04.0/Resource/Font/*'
cp ghostpdl-10.04.0/Resource/Font/*.pfb ghostscript/fonts/
cp ghostpdl-10.04.0/Resource/Font/*.afm ghostscript/fonts/
cp ghostpdl-10.04.0/Resource/Font/*.pfm ghostscript/fonts/
rm -rf ghostpdl-10.04.0 ghostpdl-10.04.0.tar.gz
Commit everything together:
ghostscript/gs
ghostscript/fonts/*.pfb
ghostscript/fonts/*.afm
ghostscript/fonts/*.pfm
Deploy to Laravel Cloud
Create deploy/ghostscript.sh:
#!/bin/bash
set -e
GS_DIR="$HOME/ghostscript"
mkdir -p "$GS_DIR/fonts"
cp ghostscript/gs "$GS_DIR/gs"
chmod +x "$GS_DIR/gs"
cp ghostscript/fonts/* "$GS_DIR/fonts/"
echo "Ghostscript installed:"
"$GS_DIR/gs" --version
This copies the binary and fonts from your repo into $HOME/ghostscript. On Laravel Cloud that ends up under /var/www/ghostscript.
Then in your Laravel Cloud Build Commands:
# Install Ghostscript
./deploy/ghostscript.sh
# Composer
composer install --no-dev
After deploy, the build log should show:
Ghostscript installed:
10.04.0
Using it in PHP
Then call it like any other binary:
use Illuminate\Support\Facades\Process;
$gsPath = '/var/www/ghostscript/gs';
$fontDir = '/var/www/ghostscript/fonts';
$result = Process::run([
$gsPath,
'-dBATCH', '-dNOPAUSE', '-q',
'-sDEVICE=pdfwrite',
'-sFONTPATH=' . $fontDir,
'-sOutputFile=' . $outputPath,
$input1, $input2,
]);
Gotchas
- Build for arm64: if you build on an x86 machine, keep
--platform linux/arm64in the Docker command or you’ll end up with the wrong binary. - Don’t forget the fonts: this was the real gotcha for me. The binary worked, but some PDFs didn’t until I shipped the URW fonts too.
- Always pass
-sFONTPATH: otherwisegswon’t find those fonts. - Binary size in git: around 23MB for
gs, plus the fonts. Fine for an internal tool. For a bigger setup I’d probably fetch it from S3 during build.
And that’s it. A static binary, some fonts, and a deploy script.
I no longer have comments enabled here, but you can reach me on X.