Mengontainerisasi aplikasi NextPDF
Sekilas pandang
Bagian berjudul “Sekilas pandang”Anda menginginkan sebuah image Docker yang kecil dan reproducible yang menjalankan
native engine NextPDF core yang berjalan in-process — composer require nextpdf/core, menghasilkan PDF di dalam proses PHP Anda. Halaman ini membangun
persis itu: sebuah image php:8.4 dengan hanya ekstensi yang benar-benar
dibutuhkan engine, tanpa dependensi pengembangan di layer akhir, font terbundel,
pengguna runtime non-root, opcache yang disetel untuk produksi, dan langkah
verifikasi yang menggagalkan build jika ada yang hilang.
Halaman ini hanya untuk native engine. Chrome bridge (writeHtmlChrome
melalui nextpdf/artisan) dan server Connect adalah runtime terpisah dengan
image-nya sendiri yang lebih berat — instalasi headless Chromium untuk bridge,
sebuah layanan yang berjalan lama untuk Connect. Jangan menambahkan browser atau
server ke image ini; native engine tidak membutuhkan keduanya.
Sebelum Anda mulai, pastikan bagian-bagian ini telah tersedia:
- Aplikasi Anda memiliki
composer.jsondancomposer.lockyang ter-commit, dengannextpdf/coresebagai dependensi. - Anda memiliki berkas font yang ingin Anda sematkan, dan Anda berlisensi untuk menyematkannya.
- Anda dapat menjalankan
docker buildterhadap direktori aplikasi Anda.
Ini adalah how-to operasi. Hampir tidak ada PHP di sini; pekerjaannya adalah Dockerfile dan beberapa pengaturan lingkungan.
Apa yang benar-benar dibutuhkan engine
Bagian berjudul “Apa yang benar-benar dibutuhkan engine”Image harus memenuhi constraint platform nyata engine, tidak lebih. Membacanya
langsung dari paket, nextpdf/core membutuhkan php: >=8.4 <9.0 dan ekstensi PHP
berikut:
| Ekstensi | Mengapa engine membutuhkannya |
|---|---|
ext-mbstring | Penanganan string multi-byte untuk teks dan pengkodean |
ext-intl | Dukungan Unicode, locale, dan internasionalisasi |
ext-gd | Pendekodean dan pemrosesan gambar raster |
ext-openssl | Kriptografi untuk penandatanganan dan hashing aman |
ext-zlib | Kompresi stream (Flate) atas object PDF |
ext-curl | Klien HTTP untuk panggilan keluar engine |
Petakan itu ke image resmi php:8.4. openssl, curl, dan zlib sudah
dikompilasi ke dalam image PHP resmi, jadi Anda tidak
docker-php-ext-install keduanya. mbstring, gd, dan intl tidak
dibundel dan harus dipasang, dan masing-masing membutuhkan system development
header-nya hadir terlebih dahulu — mbstring selain itu membutuhkan dependensi
build libonig-dev (Oniguruma). Jangan menambahkan ekstensi engine yang tidak
dicantumkan paket — setiap docker-php-ext-install ekstra adalah build time dan
attack surface yang tidak Anda butuhkan. Satu ekstensi non-engine yang dipasang
image ini adalah opcache: ia adalah ekstensi performa runtime, tidak dibundel
dalam keadaan aktif pada image resmi, dan penyetelan opcache di bawah bergantung
pada keberadaannya (lihat “Opcache untuk produksi”).
Dockerfile produksi
Bagian berjudul “Dockerfile produksi”Ini adalah build dua-stage. Stage pertama memasang dependensi Composer dengan paket pengembangan dikecualikan; stage kedua adalah image runtime ramping yang dikirim.
Pertama, tambahkan sebuah .dockerignore di samping Dockerfile. Tugas utamanya
adalah menjaga lingkungan host — sebuah vendor/ yang dibangun di host, berkas
secret lokal, dan cache build — sepenuhnya keluar dari konteks build, sehingga
COPY . /var/www/app hanya mengirim apa yang Anda inginkan: build yang lebih
kecil, lebih cepat, dan lebih aman yang tidak dapat membocorkan secret .env
lokal atau membawa megabyte vendor/ host ke dalam image.
Mengecualikan vendor/ juga penting karena COPY sebuah direktori adalah
merge, bukan replace. Dockerfile di bawah menjalankan
RUN rm -rf /var/www/app/vendor sebelum COPY --from=vendor ... /var/www/app/vendor,
sehingga dalam image ini sebuah vendor/ host tidak pernah dapat bertahan di
bawah pohon dependensi yang bersih. Tetapi jika Anda pernah menghapus pengaman
rm -rf itu, sebuah vendor/ yang dibangun di host dalam konteks akan mendarat
lebih dulu dan salinan vendor-stage hanya akan menimpa path yang dimuat pohon
bersih — berkas host ekstra apa pun (sebuah paket usang atau yang dipasang via
dev, sebuah kelas yatim) akan kemudian bertahan di bawahnya. Menjaga vendor/
keluar dari konteks menutup celah itu terlepas dari rm -rf.
# .dockerignore — keep the host environment out of the build context.vendor/.git/.env.env.local.env.*.localvar/cache/storage/node_modules/*.logKecualikan berkas secret lokal yang sebenarnya (.env, .env.local,
.env.*.local), bukan sebuah .env.* menyeluruh — wildcard itu juga membuang
template non-secret seperti .env.example yang memang ingin Anda kirim
sehingga image membawa baseline konfigurasi yang terdokumentasi. Pertahankan
template env non-secret yang ter-commit dalam konteks; kecualikan hanya berkas
yang benar-benar memuat secret lokal.
# 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"]Stage dependensi berjalan dengan --no-scripts sehingga tidak ada hook
post-install aplikasi yang berjalan terhadap pohon yang tidak lengkap; jalankan
langkah build aplikasi apa pun (kompilasi aset, pemanasan cache) di stage
selanjutnya setelah kode disalin.
Instalasi Composer multi-stage (tanpa dependensi dev)
Bagian berjudul “Instalasi Composer multi-stage (tanpa dependensi dev)”Image yang dikirim tidak boleh memuat perkakas pengembangan. Flag --no-dev pada
composer install adalah baris yang load-bearing: ia melewati segala sesuatu di
bawah require-dev pada nextpdf/core dan aplikasi Anda — test runner, static
analyzer, dan alat mutasi — yang tidak satu pun memiliki tempat di produksi.
Pasangkan dengan --optimize-autoloader sehingga autoloader adalah class map yang
dihasilkan alih-alih pemindaian filesystem pada setiap permintaan.
Salin composer.json dan composer.lock sebelum sisa source sehingga Docker
men-cache layer dependensi dan hanya me-resolve ulang ketika berkas lock berubah.
Karena instalasi pertama itu berjalan terhadap berkas lock saja — tanpa source
aplikasi — --optimize-autoloader di sana membangun class map untuk pohon
vendor saja; kelas-kelas aplikasi Anda sendiri belum ada. Itulah mengapa
stage runtime menjalankan
composer dump-autoload --optimize --no-dev --no-scripts sekali setelah
menyalin source: ia melipat kelas-kelas aplikasi ke dalam class map teroptimasi
yang sama. Jangan menjalankan composer dump-autoload terpisah di sebuah worktree
yang juga Anda kembangkan (ia akan meng-commit class map produksi ke pohon dev);
rebuild itu berada di dalam image, setelah salinan source, seperti ditunjukkan di
atas.
Bundel font ke dalam image
Bagian berjudul “Bundel font ke dalam image”Native engine me-resolve font dari berkas font yang dapat dibacanya, bukan
dari font terpasang OS. Memasang paket fonts-* atau menjalankan fc-cache tidak
melakukan apa pun yang dapat dilihat jalur native, sehingga image ini tidak
memasang font sistem. Bundel berkas .ttf / .otf Anda di bawah
resources/fonts/; COPY . /var/www/app di atas sudah membawanya ke dalam image.
Memasukkan berkas ke dalam image hanyalah separuh pekerjaan. Native engine polos
tidak membaca variabel lingkungan pencarian-font — NEXTPDF_FONTS_PATH adalah
nilai default dari kunci config fonts_path paket nextpdf/laravel
(env('NEXTPDF_FONTS_PATH', resource_path('fonts'))) dan hanya dikonsumsi oleh
integrasi framework itu, bukan oleh nextpdf/core. Sebuah entrypoint
php bin/generate.php polos dengan hanya variabel itu yang disetel tidak
mendaftarkan font dan merender tofu yang sama yang ingin dicegah keberadaan image
ini. Entrypoint harus mendaftarkan direktori terbundel di PHP:
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();Itulah seluruh urusan Docker untuk font. Aturan penamaan berkas, API registry, pola warmup-and-lock, dan penanganan filesystem read-only semuanya berada di halaman tersendiri — jangan menduplikasinya di sini. Baca Menyediakan font untuk native engine di produksi untuk pola selengkapnya, dan daftarkan direktori yang sama yang Anda bundel.
Jalankan sebagai pengguna non-root
Bagian berjudul “Jalankan sebagai pengguna non-root”Image PHP resmi berjalan sebagai root secara default. Sebuah generator PDF tidak
membutuhkan root, jadi buat pengguna tanpa hak istimewa dan beralih ke sana.
Dockerfile di atas menambahkan pengguna sistem appuser dengan UID tinggi yang
tetap (10001), memberinya kepemilikan pohon aplikasi, dan berakhir dengan
USER appuser sehingga setiap proses yang dimulai kontainer tanpa hak istimewa.
Jaga aplikasi tetap read-only saat runtime jika memungkinkan. Engine membaca
berkas font-nya dan hanya menulis keluarannya dan sebuah cache font-terurai
opsional, sehingga sebuah kontainer readOnlyRootFilesystem bekerja selama path
keluaran dan direktori cache apa pun adalah mount yang dapat ditulis. Gabungkan
ini dengan Linux capabilities yang dibuang dan sebuah flag no-new-privileges
pada orchestrator Anda untuk defense in depth.
Opcache untuk produksi
Bagian berjudul “Opcache untuk produksi”Opcache bermanfaat untuk PHP worker yang berumur panjang — sebuah pool FPM
atau proses Apache mod_php yang melayani banyak permintaan dari satu proses yang
hangat. Proses-proses itu mengompilasi kelas Anda sekali lalu tidak pernah men-stat
berkas source pada hot path, yang persis dibeli oleh
opcache.validate_timestamps=0. Opcache tidak aktif secara default pada image
resmi php:8.4, sehingga Dockerfile di atas memasangnya dengan
docker-php-ext-install opcache (Anda dapat setara dengan
docker-php-ext-enable opcache jika ekstensi sudah dikompilasi). Berkas conf.d
di bawah adalah penyetelan, bukan langkah pengaktifan — ia tidak melakukan apa pun
hingga ekstensi dimuat. Kirim sebagai sebuah include conf.d
(docker/opcache.ini, disalin dalam Dockerfile):
opcache.enable=1opcache.enable_cli=0opcache.memory_consumption=192opcache.interned_strings_buffer=16opcache.max_accelerated_files=20000opcache.validate_timestamps=0opcache.validate_timestamps=0 berarti cache tidak pernah memeriksa ulang berkas
source — benar untuk image imutabel, karena satu-satunya cara kode berubah adalah
sebuah image baru. Setel memory_consumption dan max_accelerated_files ke
jumlah kelas aplikasi Anda.
CMD yang ditunjukkan adalah generator CLI sekali-jalan, dan
opcache.enable_cli=0 benar untuknya. Sebuah proses php bin/generate.php
berumur pendek dimulai, mengompilasi, merender sekali, dan keluar, sehingga cache
opcode yang tidak dapat dibagikannya dengan permintaan berikutnya tidak memberi
manfaat — biarkan opcache CLI mati dan tidak membayar biaya memorinya. Opcache
hanya bermanfaat di tempat prosesnya digunakan kembali: sebuah SAPI FPM/Apache,
atau worker CLI yang benar-benar berjalan lama (sebuah konsumen queue atau server
gaya RoadRunner). Hanya worker CLI residen semacam itu yang akan menyetel
opcache.enable_cli=1; untuk generator sekali-jalan di sini, biarkan 0.
Jika Anda memang menjalankan setup yang menggunakan preloading opcache (sebuah
worker FPM berumur panjang dengan skrip opcache.preload), setel
opcache.preload=/path/to/preload.php dan tambahkan
opcache.preload_user=appuser sehingga preload berjalan sebagai pengguna tanpa
hak istimewa. Tanpa sebuah skrip opcache.preload yang sebenarnya,
opcache.preload_user tidak melakukan apa pun, itulah mengapa ia tidak ada di
config baseline di atas — jangan menambahkannya kecuali Anda juga menyetel
opcache.preload.
Verifikasi image
Bagian berjudul “Verifikasi image”Tambahkan langkah verifikasi sehingga image yang salah-build gagal dengan lantang
alih-alih menghasilkan tofu atau fatal pada permintaan pertama. NextPDF mengirim
sebuah CLI yang perintah doctor-nya memeriksa lingkungan PHP yang berjalan dan
melaporkan persis ekstensi yang diperhatikan engine — openssl, zlib,
mbstring, gd, curl, dan intl. Paket mendeklarasikan
"bin": ["bin/nextpdf"], sehingga dalam aplikasi yang mengonsumsi Composer
memasang executable di vendor/bin/nextpdf (bukan bin/nextpdf, yang
merupakan path di dalam paket nextpdf/core itu sendiri). Jalankan di dalam image
yang dibangun:
docker run --rm your-app:latest php vendor/bin/nextpdf doctorHasil yang sehat mengonfirmasi PHP 8.4 dan setiap ekstensi yang diperlukan dimuat. Kaitkan pemanggilan yang sama ke dalam build (atau sebuah CI smoke job) sehingga ekstensi yang hilang menghentikan pipeline:
# Fail the pipeline if the engine's environment is not healthy.docker run --rm your-app:latest php vendor/bin/nextpdf doctor || exit 1Untuk pemeriksaan end-to-end, render satu halaman melalui entrypoint Anda sendiri dan tegaskan pada keluarannya, seperti yang dijelaskan halaman font untuk sebuah pemeriksaan smoke font.
Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”php:8.4-fpmatau-apachealih-alih-cli. Gunakan SAPI yang benar-benar dilayani aplikasi Anda. List ekstensi identik; hanya tag base danCMD/entrypoint yang berbeda. Untuk queue worker atau batch job CLI,-clibenar.- Alpine (
php:8.4-alpine) membutuhkan nama paket yang berbeda. Barisapt-getdi atas adalah untuk image default berbasis Debian. Pada Alpine, pasang header*-devsebagai sebuah grup build virtual (apk add --no-cache --virtual .build-deps icu-dev libpng-dev freetype-dev libjpeg-turbo-dev oniguruma-dev) dan, setelah langkahdocker-php-ext-install gd intl mbstring opcache,apk del .build-deps— tetapi pertamaapk add --no-cachelibrary runtime yang ditautkan ekstensi (icu-libs,libpng,freetype,libjpeg-turbo,oniguruma) sehingga menghapus grup build tidak meng-unlinkintl.so/gd.so/mbstring.so. Ini adalah aturan keep-the-runtime-libs yang sama yang ditegakkan blok Debian denganapt-mark. - Jangan memasang paket
fonts-*. Mereka tidak terlihat oleh native engine. Bundel berkas font sebagai gantinya — lihat halaman font yang ditautkan di atas. - Premium dan ionCube adalah urusan image yang berbeda. Build NextPDF Pro / Enterprise yang dienkode ionCube membutuhkan ionCube Loader terpasang di image dan dicocokkan dengan build PHP persis kontainer (8.4, NTS vs. ZTS). Itu berada di luar cakupan image core; jika Anda men-deploy premium, ikuti bagian Docker dari Pengaturan ionCube Loader.
- Jaga sebuah
vendor/host keluar dari konteks build..dockerignore(mengecualikanvendor/,.git/, dan cache lokal) menjaga pohon host keluar dari konteks sepenuhnya — itulah yang membuat build kecil, cepat, dan bebas dari secret lokal yang bocor. Ia juga menjaga kasus directory-merge: sebuahCOPYdirektori adalah merge, bukan replace, sehingga sebuahvendor/yang dibangun di host yang mencapai konteks akan mendarat lebih dulu danCOPY --from=vendor /app/vendor /var/www/app/vendorhanya akan menimpa path yang dimuat pohon dependensi bersih. Dalam Dockerfile ini,RUN rm -rf /var/www/app/vendorsebelum salinan vendor sudah menghapus direktori semacam itu, sehingga residu itu tidak dapat terjadi di sini; risiko merge hanya kembali jika Anda menghapus pengamanrm -rfitu, itulah mengapa pengecualian.dockerignoreadalah perbaikan yang tahan lama.
Catatan keamanan
Bagian berjudul “Catatan keamanan”- Jangan kirim dependensi dev.
--no-devmenjaga perkakas test dan analisis, serta paket transitifnya, keluar dari image runtime dan attack surface-nya. - Jalankan tanpa hak istimewa.
USER appuserakhir memastikan tidak ada proses kontainer yang berjalan sebagai root. Pasangkan dengan root filesystem read-only dan capabilities yang dibuang pada orchestrator Anda. - Pin base image. Pin
php:8.4ke sebuah digest di produksi sehingga sebuah rebuild tidak dapat diam-diam menarik base yang berubah, dan rebuild pada kadensi untuk mengambil patch keamanan secara sengaja. - Jaga font dan lisensi keluar dari layer publik. Bundel hanya font yang Anda berlisensi untuk menyematkannya, dan jangan pernah menanamkan berkas lisensi premium ke dalam image yang di-push secara publik — mount saat runtime sebagai gantinya.
Konformitas
Bagian berjudul “Konformitas”Panduan ini tidak membuat klaim standar normatif. Fakta platform dibaca langsung
dari paket nextpdf/core: constraint php: >=8.4 <9.0 dan ekstensi yang
diperlukan ext-mbstring, ext-intl, ext-gd, ext-openssl, ext-zlib, dan
ext-curl. Perintah verifikasi adalah handler doctor CLI nextpdf yang
sebenarnya — dideklarasikan sebagai "bin": ["bin/nextpdf"] pada nextpdf/core
dan karena itu terpasang di vendor/bin/nextpdf pada aplikasi yang mengonsumsi —
yang melaporkan set ekstensi yang sama. Native engine mendaftarkan font melalui
NextPDF\Typography\FontRegistry (argumen constructor direktori /
addFontDirectory()) yang dikaitkan via NextPDF\Core\DocumentFactory;
NEXTPDF_FONTS_PATH adalah kunci config fonts_path paket nextpdf/laravel
(env('NEXTPDF_FONTS_PATH', resource_path('fonts'))), bukan variabel yang dibaca
nextpdf/core. Perilaku registry didokumentasikan pada halaman font yang
ditautkan di bawah Lihat juga.
Lihat juga
Bagian berjudul “Lihat juga”- Menyediakan font untuk native engine di produksi: penamaan berkas-font, API registry, dan pola warmup-and-lock yang diandalkan image ini.
- Mengalirkan PDF besar yang dihasilkan sebagai respons HTTP: model memori untuk menyajikan dokumen yang dibangun dari sebuah controller framework.
- Merender di edge dengan Cloudflare: ketika kontainer in-process bukan runtime yang tepat.
- Pengaturan ionCube Loader: urusan image terpisah untuk build premium yang dienkode ionCube.