Een NextPDF-applicatie containeriseren
In één oogopslag
Sectie met titel “In één oogopslag”Je wilt een kleine, reproduceerbare Docker-image die de native, in-process
NextPDF core-engine draait — composer require nextpdf/core, die PDF’s genereert
binnen je PHP-proces. Deze pagina bouwt precies dat: een php:8.4-image met alleen
de extensies die de engine daadwerkelijk nodig heeft, geen
ontwikkelingsafhankelijkheden in de laatste laag, meegeleverde lettertypen, een
non-root-runtime-gebruiker, opcache afgestemd op productie, en een verificatiestap
die de build laat falen als er iets ontbreekt.
Deze pagina is alleen voor de native engine. De Chrome-brug
(writeHtmlChrome via nextpdf/artisan) en de Connect-server zijn aparte runtimes
met hun eigen, zwaardere images — een headless-Chromium-installatie voor de brug,
een langlevende service voor Connect. Voeg geen browser of server toe aan deze
image; de native engine heeft geen van beide nodig.
Bevestig voordat je begint dat deze onderdelen op hun plek staan:
- Je applicatie heeft een gecommitte
composer.jsonencomposer.lock, metnextpdf/coreals afhankelijkheid. - Je hebt de lettertypebestanden die je wilt insluiten, en je hebt het recht ze in te sluiten.
- Je kunt
docker builduitvoeren tegen je applicatiemap.
Dit is een operationele how-to. Er is hier vrijwel geen PHP; het werk is de Dockerfile en een paar omgevingsinstellingen.
Wat de engine daadwerkelijk vereist
Sectie met titel “Wat de engine daadwerkelijk vereist”De image moet voldoen aan de echte platformbeperkingen van de engine, niet meer.
Rechtstreeks uit het pakket gelezen vereist nextpdf/core php: >=8.4 <9.0 en
deze PHP-extensies:
| Extensie | Waarom de engine die nodig heeft |
|---|---|
ext-mbstring | Multi-byte string-afhandeling voor tekst en coderingen |
ext-intl | Unicode-, locale- en internationaliseringsondersteuning |
ext-gd | Decodering en verwerking van rasterafbeeldingen |
ext-openssl | Cryptografie voor ondertekenen en veilig hashen |
ext-zlib | Stream-compressie (Flate) van PDF-objecten |
ext-curl | HTTP-client voor de uitgaande aanroepen van de engine |
Breng die in kaart op de officiële php:8.4-image. openssl, curl en zlib
zijn al ingecompileerd in de officiële PHP-image, dus die hoef je niet te
docker-php-ext-install-en. mbstring, gd en intl zijn niet meegeleverd
en moeten worden geïnstalleerd, en elk heeft eerst zijn systeem-development-headers
nodig — mbstring heeft daarnaast de build-afhankelijkheid libonig-dev
(Oniguruma) nodig. Voeg geen engine-extensies toe die het pakket niet vermeldt —
elke extra docker-php-ext-install is buildtijd en aanvalsoppervlak dat je niet
nodig hebt. De enige niet-engine-extensie die deze image wel installeert is
opcache: het is een runtime-prestatie-extensie, niet standaard ingeschakeld op
de officiële image, en de opcache-afstemming hieronder hangt af van zijn
aanwezigheid (zie “Opcache voor productie”).
De productie-Dockerfile
Sectie met titel “De productie-Dockerfile”Dit is een two-stage-build. De eerste stage installeert Composer-afhankelijkheden met de ontwikkelingspakketten uitgesloten; de tweede stage is de slanke runtime-image die wordt verscheept.
Voeg eerst een .dockerignore toe naast de Dockerfile. De primaire taak ervan is
de host-omgeving — een op de host gebouwde vendor/, lokale secret-bestanden en
build-caches — volledig buiten de buildcontext te houden, zodat
COPY . /var/www/app alleen verscheept wat je bedoelt: kleinere, snellere en
veiligere builds die geen lokale .env-secrets kunnen lekken of megabytes
host-vendor/ in de image kunnen dragen.
Het uitsluiten van vendor/ is ook van belang omdat een map-COPY een merge
is, geen replace. De Dockerfile hieronder voert RUN rm -rf /var/www/app/vendor
uit vóór de COPY --from=vendor ... /var/www/app/vendor, dus in deze image kan
een host-vendor/ nooit overleven onder de schone afhankelijkheidsboom. Maar als
je die rm -rf-beveiliging ooit verwijdert, zou een op de host gebouwde vendor/
in de context als eerste landen en zou de vendor-stage-kopie alleen de paden
overschrijven die de schone boom bevat — eventuele extra host-bestanden (een
verouderd of dev-geïnstalleerd pakket, een verweesde klasse) zouden er dan
onderdoor overleven. vendor/ buiten de context houden sluit dat gat ongeacht de
rm -rf.
# .dockerignore — keep the host environment out of the build context.vendor/.git/.env.env.local.env.*.localvar/cache/storage/node_modules/*.logSluit de echte lokale secret-bestanden uit (.env, .env.local, .env.*.local),
geen pauschale .env.* — die wildcard verwijdert ook niet-secret-templates zoals
.env.example die je wel wilt verschepen zodat de image een gedocumenteerde
configuratiebaseline draagt. Houd elke gecommitte, niet-secret env-template in de
context; sluit alleen de bestanden uit die daadwerkelijk lokale secrets bevatten.
# syntax=docker/dockerfile:1
# ---- Stage 1: dependencies (no dev) ---------------------------------------FROM composer:2 AS vendor
WORKDIR /appCOPY composer.json composer.lock ./
# Install production dependencies only. --no-dev excludes phpunit, phpstan,# infection, and the other require-dev tooling from the shipped image.# --optimize-autoloader builds a class map for the *vendor* tree here; the# application's own classes are not present in this stage yet, so they are# optimized after the source copy in the runtime stage (see below).RUN composer install \ --no-dev \ --no-interaction \ --no-progress \ --prefer-dist \ --optimize-autoloader \ --no-scripts
# ---- Stage 2: runtime ------------------------------------------------------FROM php:8.4-cli AS runtime
# System headers for the gd, intl, and mbstring extensions that need compiling.# The PHP image already provides openssl, curl, and zlib, so those are NOT# listed; gd, intl, and mbstring are installed below. opcache has no system# headers and is installed in the same step. mbstring is built against# Oniguruma, so libonig-dev is in the *-dev set and its runtime lib (libonig5)# is preserved by the same detection below.## Build the *-dev headers (which pull in the runtime libs), compile the# extensions, then mark only the runtime shared libraries the extensions# actually link against so they survive the --auto-remove purge of the headers.# Removing libicu / libpng / libjpeg / libfreetype / libonig here would unlink# intl.so, gd.so, or mbstring.so at runtime ("undefined symbol" / "cannot open# shared object file").RUN set -eux; \ savedAptMark="$(apt-mark showmanual)"; \ apt-get update; \ apt-get install -y --no-install-recommends \ libicu-dev \ libpng-dev \ libjpeg62-turbo-dev \ libfreetype6-dev \ libonig-dev; \ docker-php-ext-configure gd --with-freetype --with-jpeg; \ docker-php-ext-install -j"$(nproc)" gd intl mbstring opcache; \ # Detect the runtime .so dependencies of the just-built extensions and # mark them manual so --auto-remove keeps them while dropping the headers. apt-mark auto '.*' > /dev/null; \ apt-mark manual $savedAptMark > /dev/null; \ find /usr/local/lib/php/extensions -type f -name '*.so' -exec \ sh -c 'ldd "$1" 2>/dev/null \ | awk "/=>/ { print \$3 }" \ | grep -E "^/" \ | xargs -r dpkg-query -S 2>/dev/null \ | cut -d: -f1 \ | sort -u \ | xargs -r apt-mark manual' _ {} \; ; \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ rm -rf /var/lib/apt/lists/*
# Production opcache settings (see the opcache section below). The opcache# extension is installed above (docker-php-ext-install opcache); this file only# tunes it.COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
# A static Composer binary for the one optimized-autoloader rebuild below. It is# copied into the build but the final stage runs no Composer at request time.COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/app
# Application code, then the vendor tree from the dependency stage. The .dockerignore# should already keep a host vendor/ out of the context; the rm here is a second line# of defense so a stale host-built vendor/ can never merge under the clean one (a# directory COPY merges, it does not replace).COPY . /var/www/appRUN rm -rf /var/www/app/vendorCOPY --from=vendor /app/vendor /var/www/app/vendor
# Now that the application source is present, regenerate the optimized class map# so the APP's own classes are in the optimized autoloader, not just the vendor# packages. --no-dev keeps require-dev out; --no-scripts avoids running# application hooks during the image build.RUN composer dump-autoload \ --optimize \ --no-dev \ --no-interaction \ --no-scripts \ && rm -f /usr/bin/composer
# The bundled fonts live at /var/www/app/resources/fonts. The native engine does# NOT read any font-path environment variable — the entrypoint registers that# directory in PHP (see "Bundle fonts into the image" below). There is no ENV# line for fonts here.
# Run as a non-root user (see the non-root section below).RUN useradd --system --no-create-home --uid 10001 appuser \ && chown -R appuser:appuser /var/www/appUSER appuser
CMD ["php", "bin/generate.php"]De afhankelijkheidsstage draait met --no-scripts zodat er geen
applicatie-post-install-hook draait tegen een onvolledige boom; voer een
applicatie-buildstap (asset-compilatie, cache-warming) uit in een latere stage
nadat de code is gekopieerd.
Multi-stage Composer-install (geen dev-afhankelijkheden)
Sectie met titel “Multi-stage Composer-install (geen dev-afhankelijkheden)”De verscheepte image mag geen ontwikkelingstooling bevatten. De --no-dev-vlag op
composer install is de dragende regel: het slaat alles onder require-dev in
nextpdf/core en je applicatie over — de test runner, statische analyzer en
mutatietools — die geen enkele plaats hebben in productie. Combineer het met
--optimize-autoloader zodat de autoloader een gegenereerde class map is in plaats
van een filesystem-scan bij elke request.
Kopieer composer.json en composer.lock vóór de rest van de bron zodat
Docker de afhankelijkheidslaag cachet en alleen opnieuw oplost wanneer het
lock-bestand verandert. Omdat die eerste install draait tegen het lock-bestand
alleen — zonder de applicatiebron — bouwt --optimize-autoloader daar de class map
alleen voor de vendor-boom; de eigen klassen van je applicatie zijn er nog niet.
Daarom draait de runtime-stage eenmaal
composer dump-autoload --optimize --no-dev --no-scripts nadat de bron is
gekopieerd: het vouwt de klassen van de app in dezelfde geoptimaliseerde class map.
Draai geen aparte composer dump-autoload in een worktree waarin je ook
ontwikkelt (dat zou een productie-class-map in een dev-boom committen); de herbouw
hoort in de image, na het kopiëren van de bron, zoals hierboven getoond.
Lettertypen in de image meeleveren
Sectie met titel “Lettertypen in de image meeleveren”De native engine lost lettertypen op uit lettertypebestanden die het kan lezen,
niet uit OS-geïnstalleerde lettertypen. fonts-*-pakketten installeren of
fc-cache draaien doet niets wat het native pad kan zien, dus deze image
installeert geen systeemlettertypen. Lever je .ttf- / .otf-bestanden mee onder
resources/fonts/; de COPY . /var/www/app hierboven draagt ze al in de image.
De bestanden in de image krijgen is slechts de helft van het werk. De kale native
engine leest geen lettertype-zoek-omgevingsvariabele — NEXTPDF_FONTS_PATH is
de standaardwaarde van de fonts_path-configuratiekey van het
nextpdf/laravel-pakket (env('NEXTPDF_FONTS_PATH', resource_path('fonts'))) en
wordt alleen door die framework-integratie verbruikt, niet door nextpdf/core. Een
gewone php bin/generate.php-entrypoint met alleen die variabele ingesteld
registreert geen lettertypen en rendert dezelfde tofu die deze image moet
voorkomen. De entrypoint moet de meegeleverde map in PHP registreren:
use NextPDF\Typography\FontRegistry;use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;
// Register the directory the Dockerfile bundled the fonts into.$registry = new FontRegistry('/var/www/app/resources/fonts');// (equivalently, $registry->addFontDirectory('/var/www/app/resources/fonts');)
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));$doc = $factory->create();Dat is de hele Docker-zorg voor lettertypen. De bestandsnaamregels, de register-API, het warmup-and-lock-patroon en de read-only-filesystem-afhandeling leven allemaal op de speciale pagina — dupliceer ze hier niet. Lees Lettertypen voorzien voor de native engine in productie voor het volledige patroon, en registreer dezelfde map die je hebt meegeleverd.
Draaien als een non-root-gebruiker
Sectie met titel “Draaien als een non-root-gebruiker”De officiële PHP-images draaien standaard als root. Een PDF-generator heeft geen
root nodig, dus maak een onbevoorrechte gebruiker en schakel daarnaar over. De
Dockerfile hierboven voegt een systeemgebruiker appuser toe met een vaste hoge
UID (10001), geeft die eigenaarschap over de applicatieboom, en eindigt met
USER appuser zodat elk proces dat de container start onbevoorrecht is.
Houd de applicatie waar mogelijk read-only tijdens runtime. De engine leest zijn
lettertypebestanden en schrijft alleen zijn uitvoer en een optionele cache met
geparste lettertypen, dus een readOnlyRootFilesystem-container werkt zolang het
uitvoerpad en eventuele cache-map schrijfbare mounts zijn. Combineer dit met
gedropte Linux-capabilities en een no-new-privileges-vlag in je orchestrator voor
diepteverdediging.
Opcache voor productie
Sectie met titel “Opcache voor productie”Opcache loont voor langlevende PHP-workers — een FPM-pool of een Apache
mod_php-proces dat veel requests bedient vanuit één warm proces. Die processen
compileren je klassen eenmaal en stat’en daarna nooit bronbestanden op een hot
path, wat precies is wat opcache.validate_timestamps=0 je oplevert. Opcache is
niet standaard ingeschakeld op de officiële php:8.4-image, dus de Dockerfile
hierboven installeert het met docker-php-ext-install opcache (je kunt
gelijkwaardig docker-php-ext-enable opcache als de extensie al is gecompileerd).
Het conf.d-bestand hieronder is afstemming, niet de inschakelstap — het doet
niets totdat de extensie is geladen. Verscheep het als een conf.d-include
(docker/opcache.ini, gekopieerd in de Dockerfile):
opcache.enable=1opcache.enable_cli=0opcache.memory_consumption=192opcache.interned_strings_buffer=16opcache.max_accelerated_files=20000opcache.validate_timestamps=0opcache.validate_timestamps=0 betekent dat de cache nooit bronbestanden
hercontroleert — correct voor een immutable image, omdat de enige manier waarop de
code verandert een nieuwe image is. Stem memory_consumption en
max_accelerated_files af op het klassenaantal van je applicatie.
De getoonde CMD is een one-shot CLI-generator, en opcache.enable_cli=0 is daar
correct voor. Een kortlevend php bin/generate.php-proces start, compileert,
rendert eenmaal en stopt, dus een opcode-cache die het niet met een volgende request
kan delen, levert geen voordeel — laat CLI-opcache uit en betaal niets van zijn
geheugenkost. Opcache verdient zijn loon alleen waar het proces wordt hergebruikt:
een FPM/Apache-SAPI, of een werkelijk langlevende CLI-worker (een queue consumer of
een RoadRunner-achtige server). Alleen dat soort residente CLI-worker zou
opcache.enable_cli=1 instellen; voor de one-shot-generator hier houd je het 0.
Als je wel een opzet draait die opcache-preloading gebruikt (een langlevende
FPM-worker met een opcache.preload-script), stel dan
opcache.preload=/path/to/preload.php in en voeg opcache.preload_user=appuser toe
zodat de preload draait als de onbevoorrechte gebruiker. Zonder een werkelijk
opcache.preload-script doet opcache.preload_user niets, en daarom staat het niet
in de baseline-config hierboven — voeg het niet toe tenzij je ook opcache.preload
instelt.
De image verifiëren
Sectie met titel “De image verifiëren”Voeg een verificatiestap toe zodat een verkeerd gebouwde image luid faalt in plaats
van tofu of een fatal bij de eerste request te produceren. NextPDF levert een CLI
mee waarvan het doctor-commando de draaiende PHP-omgeving inspecteert en
rapporteert over precies de extensies waar de engine om geeft — openssl, zlib,
mbstring, gd, curl en intl. Het pakket declareert "bin": ["bin/nextpdf"],
dus in een verbruikende applicatie installeert Composer het executable op
vendor/bin/nextpdf (niet bin/nextpdf, wat het pad binnen het
nextpdf/core-pakket zelf is). Voer het uit binnen de gebouwde image:
docker run --rm your-app:latest php vendor/bin/nextpdf doctorEen gezond resultaat bevestigt PHP 8.4 en dat elke vereiste extensie is geladen. Bedraad dezelfde aanroep in de build (of een CI-smoke-job) zodat een ontbrekende extensie de pipeline stopt:
# Fail the pipeline if the engine's environment is not healthy.docker run --rm your-app:latest php vendor/bin/nextpdf doctor || exit 1Render voor een end-to-end-check één pagina via je eigen entrypoint en asserteer op de uitvoer, zoals de lettertypenpagina beschrijft voor een lettertype-smoke-check.
Randgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”php:8.4-fpmof-apachein plaats van-cli. Gebruik de SAPI waaronder je app daadwerkelijk bediend wordt. De extensielijst is identiek; alleen de base-tag en deCMD/entrypoint verschillen. Voor een queue worker of een CLI-batchjob is-clicorrect.- Alpine (
php:8.4-alpine) heeft andere pakketnamen nodig. Deapt-get-regels hierboven zijn voor de Debian-gebaseerde standaardimage. Installeer op Alpine de*-dev-headers als een virtuele buildgroep (apk add --no-cache --virtual .build-deps icu-dev libpng-dev freetype-dev libjpeg-turbo-dev oniguruma-dev) en, na dedocker-php-ext-install gd intl mbstring opcache-stap,apk del .build-deps— maar eerstapk add --no-cachede runtime-bibliotheken waartegen de extensies linken (icu-libs,libpng,freetype,libjpeg-turbo,oniguruma) zodat het verwijderen van de buildgroepintl.so/gd.so/mbstring.soniet ontlinkt. Dit is dezelfde houd-de-runtime-libs-regel die het Debian-blok metapt-markafdwingt. - Installeer geen
fonts-*-pakketten. Ze zijn onzichtbaar voor de native engine. Lever in plaats daarvan lettertypebestanden mee — zie de hierboven gelinkte lettertypenpagina. - Premium en ionCube zijn een andere image-zorg. De ionCube-gecodeerde NextPDF Pro- / Enterprise-builds hebben de ionCube Loader nodig, geïnstalleerd in de image en gematcht aan de exacte PHP-build van de container (8.4, NTS vs. ZTS). Dat valt buiten de scope voor een core-image; volg als je premium deployt de Docker-sectie van ionCube Loader instellen.
- Houd een host-
vendor/buiten de buildcontext. De.dockerignore(dievendor/,.git/en lokale caches uitsluit) houdt de host-boom volledig buiten de context — dat is wat de build klein, snel en vrij van gelekte lokale secrets maakt. Het beveiligt ook tegen het map-merge-geval: een map-COPYis een merge, geen replace, dus een op de host gebouwdevendor/die de context bereikte, zou als eerste landen en deCOPY --from=vendor /app/vendor /var/www/app/vendorzou alleen de paden overschrijven die de schone afhankelijkheidsboom bevat. In deze Dockerfile verwijdert deRUN rm -rf /var/www/app/vendorvóór de vendor-kopie al elke dergelijke map, dus dat residu kan hier niet optreden; het merge-risico keert alleen terug als je dierm -rf-beveiliging laat vallen, en daarom is de.dockerignore-uitsluiting de duurzame fix.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”- Verscheep geen dev-afhankelijkheden.
--no-devhoudt de test- en analysetooling, en hun transitieve pakketten, buiten de runtime-image en zijn aanvalsoppervlak. - Draai onbevoorrecht. De laatste
USER appuserzorgt dat geen containerproces als root draait. Combineer het met een read-only root-filesystem en gedropte capabilities in je orchestrator. - Pin de base-image. Pin
php:8.4aan een digest in productie zodat een herbouw niet stilzwijgend een gewijzigde base kan binnenhalen, en herbouw op een cadans om beveiligingspatches bewust op te pikken. - Houd lettertypen en licenties buiten publieke lagen. Lever alleen lettertypen mee die je het recht hebt in te sluiten, en bak nooit een premium-licentiebestand in een publiek gepushte image — mount het in plaats daarvan tijdens runtime.
Conformiteit
Sectie met titel “Conformiteit”Deze gids doet geen normatieve standaardenclaim. De platformfeiten worden
rechtstreeks uit het nextpdf/core-pakket gelezen: de php: >=8.4 <9.0-constraint
en de vereiste extensies ext-mbstring, ext-intl, ext-gd, ext-openssl,
ext-zlib en ext-curl. Het verificatiecommando is de echte nextpdf-CLI-doctor-
handler — gedeclareerd als "bin": ["bin/nextpdf"] in nextpdf/core en daarom
geïnstalleerd op vendor/bin/nextpdf in een verbruikende app — die rapporteert over
dezelfde extensieset. De native engine registreert lettertypen via
NextPDF\Typography\FontRegistry (het map-constructorargument / addFontDirectory())
bedraad via NextPDF\Core\DocumentFactory; NEXTPDF_FONTS_PATH is de
fonts_path-configuratiekey van het nextpdf/laravel-pakket
(env('NEXTPDF_FONTS_PATH', resource_path('fonts'))), geen variabele die
nextpdf/core leest. Het registergedrag is gedocumenteerd op de lettertypenpagina
die onder Zie ook is gelinkt.
Zie ook
Sectie met titel “Zie ook”- Lettertypen voorzien voor de native engine in productie: de lettertypebestandsnaamgeving, register-API en het warmup-and-lock-patroon waarop deze image vertrouwt.
- Een grote gegenereerde PDF streamen als een HTTP-respons: het geheugenmodel voor het bedienen van een gebouwd document vanuit een framework-controller.
- Aan de edge renderen met Cloudflare: wanneer een in-process container niet de juiste runtime is.
- ionCube Loader instellen: de aparte image-zorg voor ionCube-gecodeerde premium-builds.