NextPDF をサーバーレスプラットフォームで実行する
ネイティブのインプロセス NextPDF コアエンジンは、ほぼ理想的なサーバーレスワークロードです。それは あなたのプロセス内で動作するピュア PHP です——composer require nextpdf/core し、ドキュメントを構築し、バイトを得る。生成すべき外部バイナリも、ヘッドレスブラウザも、生かし続けるデーモンも、サイドカーサービスへのソケットもありません。PDF を構築する関数は、コールドで起動し、あなたの PHP を実行し、バイトを返して終了します。これは AWS Lambda(Bref ランタイム経由)、Google Cloud Run、AWS App Runner にきれいに対応します。
このページは、そのネイティブエンジンをこれら 3 つのランタイムにデプロイすることと、それらが課す少数の現実的な制約を扱います。
- ランタイムのファイルシステムは 永続ではありません。Lambda は書き込み可能な
/tmpのみを保証し、コンテナランタイム(Cloud Run、App Runner)はエフェメラルでコンテナスコープのファイルシステムを持ちます——いずれにせよフォントはデプロイパッケージまたはイメージの内部に同梱され、PHP で登録 される必要があります(エンジンはフォントパスの環境変数を読みません)。 - コールドスタート はオートローディングとあらゆるフォントのウォームアップの代償を払うため、
FontRegistryのウォームアップは呼び出しごとではなくコンテナごとに 1 回行います。 - パッケージサイズ、メモリー、タイムアウト は、些末なリクエストではなく、構築処理に合わせてサイジングする必要があります。
このページは ネイティブエンジン専用 です。Chrome ブリッジ(推奨される nextpdf/artisan パッケージ経由の writeHtmlChrome)は、別の、より重い物語です。symfony/process を通じてヘッドレス Chromium へシェルアウトしますが、素の Lambda zip やスリムなコンテナにはそれが含まれていません。Lambda で Chromium を実行するということは、ブラウザとその共有ライブラリを含むカスタムレイヤー、はるかに大きなパッケージ、そしてはるかに長いコールドスタートを意味します——ここでは対象外です。素のエンジンにはそれらが一切不要です。
始める前に、これらの要素が揃っていることを確認してください。
- アプリケーションが、
nextpdf/coreを依存として持つ、コミット済みのcomposer.jsonとcomposer.lockを持っている。 - 埋め込むつもりのフォントファイルがあり、それらを埋め込むライセンスを持っている。
- ターゲット向けのツールチェーンがある——Lambda には Bref CLI と
serverlessフレームワーク、Cloud Run / App Runner にはコンテナビルド。
なぜネイティブエンジンがサーバーレスに適合するのか
「なぜネイティブエンジンがサーバーレスに適合するのか」という見出しのセクションパッケージから直接読み取ると、nextpdf/core は php: >=8.4 <9.0 と少数の PHP 拡張——ext-mbstring、ext-intl、ext-gd、ext-openssl、ext-zlib、ext-curl——を必要とします。標準の Bref PHP レイヤーはこれらすべてを同梱しています。公式の php:8.4 コンテナイメージは openssl、curl、zlib を標準で提供しますが、mbstring、gd、intl は 同梱されておらず、システム依存のインストールと docker-php-ext-install による拡張の有効化が必要です(Docker デプロイガイドを参照)。Bref ではコンパイルすべき特異なものはありません。コンテナパスでは、素の エンジンのためにそれら 3 つの拡張をイメージビルドで有効化します。
適合をきれいにしているのは、エンジンが 行わない ことです。
- コアパスにサブプロセスがない。 ドキュメントを構築して
getPdfData()を呼ぶことは、端から端までインプロセスの PHP です。symfony/process依存はオプションの Chrome ブリッジのために存在するものであり、ネイティブレンダリングのためではありません——ネイティブな PDF 生成はプロセスを一切生成しません。 - 永続状態がない。 各呼び出しは新しいドキュメントを構築してバイトを返します。リクエスト間で存続しなければならないものは、ウォームなコンテナ以外にありません。コンテナはフォントのウォームアップ(後述)に活用しますが、正しさのために頼ることは決してありません。
- 書き込み可能な作業ディレクトリが不要。 エンジンはメモリー内で PDF を構築し、文字列として返します。ディスクに触れるのは あなた が
save()を呼んだときだけです。サーバーレスではそうしません——バイトを返します——ので、永続ファイルシステムの欠如が構築パスに噛みつくことはありません。
唯一の厳しい制約: 永続的で書き込み可能なファイルシステムがない
「唯一の厳しい制約: 永続的で書き込み可能なファイルシステムがない」という見出しのセクションデプロイのファイルシステムは永続ではありませんが、モデルはランタイムごとに異なります。AWS Lambda は書き込み可能な /tmp のみを保証します(デフォルト 512 MB、最大 10 GB まで設定可能)。関数ファイルシステムの残りは読み取り専用です。コンテナランタイム(Cloud Run、App Runner)は /tmp のみのモデルではなく、エフェメラルでコンテナスコープの書き込み可能なファイルシステムを持ちます ——しかしそこに書き込まれたものはコンテナがリサイクルされると失われるため、ストレージではなくスクラッチ領域です。いずれの場合も、ステージングには /tmp または設定済みのボリュームを優先し、アプリケーションイメージのパスへの書き込みを永続ストレージとして頼ることは決してしないでください。2 つの帰結が続きます。
永続的な出力を期待して save() を呼ばないでください。 NextPDF\Core\Document は save(string $path): void と getPdfData(): string の両方を公開します。サーバーレスでは getPdfData() を使い、バイトを返すかアップロードします——アプリケーションディレクトリへの書き込みを永続ストレージとして扱わないでください。ファイルをステージングしなければならない場合(たとえばオブジェクトストレージへのマルチパートアップロードのため)は、/tmp(または設定済みのボリューム)の下に書き込んで後始末をします。ウォームなコンテナではこのスクラッチ領域が呼び出しをまたいで存続し、そのサイズ上限に算入されることを忘れないでください。
use NextPDF\Core\Document;
// Right for serverless: get the bytes, return or upload them.$pdf = $document->getPdfData(); // string of PDF bytes, built in memory
// Avoid on serverless: save() writes to disk. On Lambda the application// directory is read-only; on Cloud Run / App Runner it is writable but// ephemeral (lost on container recycle). Neither is durable storage.// $document->save('/var/task/out.pdf'); // not durable — return the bytes insteadランタイムに OS フォントをインストールせず、自動フォント検出にも頼らず、本番ではフォントファイルを同梱してください。 Lambda では読み取り専用ファイルシステムが apt-get install fonts-* を完全にブロックします。コンテナランタイムでは、ランタイムでのインストールはエフェメラルなファイルシステムに着地し、次のリサイクルで失われます。そしてどのみち役に立ちません。なぜなら、ネイティブエンジンは OS/fontconfig のフォントを読まないからです——あなたが登録したファイルからのみフォントを解決します。したがって本番では、フォントファイルはデプロイ成果物の内部に同梱されなければなりません。意図的にフォントファイルを /tmp または設定済みのボリュームへ取得する場合は、それらをフォントレジストリに明示的に登録し、追加のコールドスタートコストと信頼性コストを受け入れる必要があります——これは推奨される本番パターンではありません。
パッケージまたはイメージにフォントを同梱して登録する
「パッケージまたはイメージにフォントを同梱して登録する」という見出しのセクションネイティブエンジンは、fontconfig や OS にインストールされたフォントからではなく、NextPDF\Typography\FontRegistry を通じて フォントファイル からフォントを解決します。サーバーレスではこれは譲れません。デプロイ後にフォントを置く永続ファイルシステムが存在しないため、フォントはパッケージ(Lambda zip またはレイヤー)の内部か、イメージ(Cloud Run / App Runner)の内部に同梱されます。
.ttf / .otf / .ttc ファイルをプロジェクト内のディレクトリ——resources/fonts/ が慣例です——の下に同梱し、それらが成果物に含まれるようにします。次に、そのディレクトリを PHP で登録します。エンジンはフォントパスの環境変数を 読みません。NEXTPDF_FONTS_PATH は nextpdf/laravel パッケージの fonts_path 設定キーのデフォルト値(env('NEXTPDF_FONTS_PATH', resource_path('fonts')))であり、nextpdf/core ではなく、そのフレームワーク統合によってのみ消費されます。素の関数は、同梱したディレクトリでレジストリを構築しなければなりません。
use NextPDF\Typography\FontRegistry;use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;
// Register the directory the deployment artifact bundled the fonts into.// On Lambda/Bref the code root is /var/task; adjust for your runtime.$registry = new FontRegistry(__DIR__ . '/resources/fonts');// (equivalently, $registry->addFontDirectory(__DIR__ . '/resources/fonts');)
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));$document = $factory->create();フォントに関するサーバーレスの懸念はこれですべてです。ファイル命名規則、完全なレジストリ API、非永続ファイルシステムの取り扱いは専用ページにあります——ここで重複させないでください。完全なパターンは 本番でネイティブエンジン向けにフォントをプロビジョニングする を読み、同梱したのと同じディレクトリを登録してください。Docker デプロイガイド は、Cloud Run / App Runner のケースに相当するイメージ側の同梱を扱います。
コールドスタート: FontRegistry をコンテナごとに 1 回ウォームアップする
「コールドスタート: FontRegistry をコンテナごとに 1 回ウォームアップする」という見出しのセクションコールドスタートは、PHP のブートストラップ、Composer の最適化済みオートローダー、そして最初の構築が引き起こすあらゆるフォント解析の代償を払います。ブートストラップは避けられませんが、フォント処理をホットパスから外して、ウォームな呼び出しをまたいで再利用することはできます。
FontRegistry と DocumentFactory を ハンドラーの外で 1 回だけ 構築し、コンテナの寿命の間それらが存続して、すべてのウォームな呼び出しで再利用されるようにします。任意で、使うとわかっているフォントファイルを指定して warmup() を呼ぶと、それらが最初のレンダリング時ではなく初期化時に解析されます。その後 lock() でレジストリをロックすると、解析済みの状態が凍結され、呼び出しごとのミューテーションが競合できなくなります。
use NextPDF\Typography\FontRegistry;use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;
// Container-scoped, built once at cold start (module scope, not per request).$fontsDir = __DIR__ . '/resources/fonts';$registry = new FontRegistry($fontsDir);
// Parse the fonts you will actually use now, so the first render does not.$registry->warmup([ $fontsDir . '/liberation/LiberationSans-Regular.ttf', $fontsDir . '/liberation/LiberationSans-Bold.ttf',]);
// Freeze the parsed state for the life of the warm container.$registry->lock();
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
// Each invocation: fresh document from the shared, warm factory.$handler = static function (array $event) use ($factory): string { $document = $factory->create(); $document->addPage(); $document->cell(0, 10, 'Hello from serverless', newLine: true);
return $document->getPdfData();};warmup() は lock() の 前 に呼んでください——レジストリはロックされると凍結されるため、その後のウォームアップは設定エラーを発生させます。ウォームアップ時に読み込みに失敗するフォントは、ランタイムの細部ではなくデプロイ時のエラー として扱ってください。ウォームするつもりのすべてのフォントパスが起動時に実際に存在して解析できることを検証し、そうでなければデプロイ(またはヘルスチェック)を失敗させてください。タイプミスしたパスを、後でグリフ欠落として表面化させてはいけません。ウォームアップのリストは、典型的な呼び出しが必要とするフォントに絞ってください。めったに使わない大きなファミリーをウォームすることは、すべてのコールドスタートを長くするだけです。
AWS Lambda 上の Bref 関数
「AWS Lambda 上の Bref 関数」という見出しのセクションBref は、公開レイヤーと serverless.yml プラグインとして、Lambda 向けの PHP ランタイムを提供します。php-84 ランタイムは nextpdf/core が必要とする拡張をすでに同梱しているため、コードとフォントをデプロイし、ハンドラーに関数を向けるだけです。最小限の serverless.yml:
service: nextpdf-serverless
provider: name: aws region: us-east-1 runtime: provided.al2023
plugins: - ./vendor/bref/bref
functions: generate: handler: handler.php description: Generate a PDF with the native NextPDF engine runtime: php-84 memorySize: 1024 # size to the build; see "Sizing" below timeout: 30 # seconds; raise for large documents # The Lambda filesystem is read-only except /tmp. Fonts ship in the # package under resources/fonts and are registered in the handler.ハンドラーは、ウォームでコンテナスコープのファクトリーでドキュメントを構築し、バイトを返します。HTTP API の場合は、API Gateway がボディをバイナリとして扱うように、application/pdf コンテンツタイプで base64 エンコードして返します。invoke またはキュートリガーの場合は、バイトをオブジェクトストレージにアップロードしてキーを返します。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// --- Cold-start: built once per container, reused across warm invocations. ---$fontsDir = __DIR__ . '/resources/fonts';$registry = new FontRegistry($fontsDir);$registry->warmup([$fontsDir . '/liberation/LiberationSans-Regular.ttf']);$registry->lock();$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
// --- Per-invocation handler. ---return static function (array $event) use ($factory): array { $document = $factory->create(); $document->addPage(); $document->cell(0, 10, 'Invoice', newLine: true);
// getPdfData() materializes the whole PDF in memory and returns it. $bytes = $document->getPdfData();
return [ 'statusCode' => 200, 'isBase64Encoded' => true, 'headers' => ['Content-Type' => 'application/pdf'], 'body' => base64_encode($bytes), ];};トラフィックを配線する前に、パッケージが健全な環境を含んでいることを検証してください。nextpdf/core は vendor/bin/nextpdf にインストールされる CLI を同梱しており、その doctor コマンドはエンジンが必要とする拡張をまさに報告します。同じランタイムイメージまたはレイヤーに対して 1 回実行し、PHP 8.4 とすべての必須拡張が存在することを確認してください。
Cloud Run と App Runner
「Cloud Run と App Runner」という見出しのセクションCloud Run と App Runner は、zip された関数ではなく コンテナ を実行するため、ビルドは Bref パッケージではなく NextPDF アプリケーションをコンテナ化する の Docker イメージです。ネイティブエンジンの制約は同一です。フォントをイメージに同梱し、同梱したディレクトリを PHP で登録し、非特権で実行し、ファイルシステムを非永続として扱います。Lambda の /tmp のみのモデルとは異なり、Cloud Run / App Runner のコンテナはエフェメラルでコンテナスコープの書き込み可能なファイルシステムを持ちます——しかしリサイクルのたびにリセットされるため、スクラッチには /tmp(Cloud Run では tmpfs)または設定済みのボリュームを使い、アプリケーションイメージのパスへの書き込みを永続ストレージとして頼ることは決してしないでください。
Lambda との違いは、構造的ではなく運用上のものです。
- コンテナはリクエストをまたいでウォームのままになれます ——コンカレンシー設定の下で。そのため、上記のコンテナスコープの
FontRegistry/DocumentFactoryウォームアップは、次の呼び出しだけでなく、多くのリクエストにわたって報われます。 - HTTP 越しに提供します(FPM または組み込みの PHP サーバー SAPI)——invoke イベントではなく。したがって、フレームワークのレスポンスを通じてバイトを返します。大きなドキュメントの場合は、ストリーミングレスポンスとして返してください——大きな生成済み PDF を HTTP レスポンスとしてストリームする を参照。
- リクエストのタイムアウトとメモリー は、関数ごとではなくサービス上に設定されます(Cloud Run のサービスタイムアウト/メモリー、App Runner のインスタンス設定)。
それ以外のすべて——拡張セット、フォント登録、getPdfData() 出力呼び出し——は、Lambda ハンドラーと同じコードです。
サイジング: パッケージ、メモリー、タイムアウト
「サイジング: パッケージ、メモリー、タイムアウト」という見出しのセクション- パッケージとイメージのサイズ。 成果物は
vendor/(本番のみ——--no-devでインストール)と同梱フォントを運びます。フォントが支配的です。完全な CJK ファミリーは数十メガバイトになります。実際にレンダリングするフォントのみを同梱して、Lambda パッケージを上限以下に、イメージを小さく保ってください。これはコールドスタートも短くします。同梱の Liberation ファミリー(resources/fonts/liberation/)は小さく、メトリック互換の Helvetica 代替をカバーします。 - メモリー。
getPdfData()は ドキュメント全体 をメモリー内で構築し、1 つの文字列として返すため、ピークメモリーはおおよそ完成した PDF 1 つのサイズに構築のワーキングセットを加えたものです。生成する最大のドキュメントに合わせて関数/コンテナのメモリーをサイジングし、平均に合わせないでください。Lambda ではメモリーが CPU もスケールするため、より多くのメモリーはしばしばより速い構築とより安価な実行を意味します——ミリ秒あたりのレートは高くても。両方を計測してください。数ページのドキュメントは 512〜1024 MB で快適です。画像が多い、またはページ数の多いドキュメントはより多くを必要とします。 - タイムアウト。 リクエスト予算を支配するのは転送ではなく構築です。関数のタイムアウトを、最悪ケースの構築時間より余裕を持って上に設定してください。ドキュメントがタイムアウトを招くほど大きい場合は、生成を非同期トリガー(キューバックの Lambda または Cloud Run ジョブ)に移し、同期リクエストをブロックする代わりに結果をオブジェクトストレージへ書き込んでください。
/tmpのサイズ。/tmpの下に何かをステージングする場合は、そのサイズ上限を勘案し、ウォームな呼び出しをまたいで存続することを忘れないでください——後始末をしないと、長寿命のコンテナが徐々に埋めてしまいます。
エッジケースと落とし穴
「エッジケースと落とし穴」という見出しのセクション- アプリディレクトリへの永続的な
save()はない。 デプロイのファイルシステムは永続ではありません——Lambda のアプリディレクトリは読み取り専用(書き込みを受け付けるのは/tmpのみ)であり、Cloud Run / App Runner のコンテナファイルシステムは書き込み可能だがエフェメラルです。getPdfData()を使ってバイトを返す/アップロードし、どうしても必要なら/tmpまたは設定済みのボリュームの下にステージングしてください。 - 自動フォント検出に頼らないでください。 ランタイムに OS フォントをインストールせず、自動フォント検出にも頼らず、本番ではフォントファイルを同梱してください。ネイティブエンジンは OS/fontconfig のフォントを読みません——あなたが登録したファイルのみを解決します。意図的にフォントファイルを
/tmpまたは設定済みのボリュームへ取得する場合は、それらをフォントレジストリに明示的に登録し、追加のコールドスタートコストと信頼性コストを受け入れる必要があります。ファイルを同梱して登録してください。上でリンクしたフォントページを参照。 NEXTPDF_FONTS_PATHは素のエンジンに対して何もしません。 それはnextpdf/laravelの設定デフォルトであり、nextpdf/coreが読む変数ではありません。その変数だけを設定する素の Bref ハンドラーはフォントを登録せず、tofu をレンダリングします。- Chrome ブリッジは素の関数に適合しません。
writeHtmlChromeはヘッドレス Chromium とsymfony/processのサブプロセスパスを必要とします。Lambda に Chromium を載せるには、ブラウザとそのライブラリを含むカスタムレイヤー、はるかに大きなパッケージ、そして長いコールドスタートが必要です。ネイティブエンジンとwriteHtmlにはそれらが一切不要です——サーバーレスではそれらを優先してください。 - コールドスタートのコストはオートロードとフォント解析です。 本番インストールでは
--optimize-autoloaderを使い、レジストリをコンテナごとに 1 回ウォームしてください。めったに使わないフォントをウォームしないでください。 - API Gateway はバイナリの扱いを必要とします。
isBase64Encoded: trueをContent-Type: application/pdfとともに返し、API がapplication/pdfをバイナリメディアタイプとして扱うように設定してください——さもないとクライアントは破損したバイトを受け取ります。 - Premium と ionCube はより重い成果物の懸念です。 ionCube エンコードされた NextPDF Pro / Enterprise ビルドは、ランタイムの正確な PHP ビルドに合致した ionCube Loader を必要としますが、ストックの Bref レイヤーはそれを含みません。それはコアのサーバーレスデプロイの対象外です。
セキュリティに関する注意
「セキュリティに関する注意」という見出しのセクション- 開発依存を出荷しないでください。
--no-devでインストールし、テストや解析のツールが関数パッケージやイメージに決して入らないようにしてください。 - 構築前に入力を検証してください。 リクエスト入力で駆動される PDF 構築はメモリー枯渇のベクトルです。範囲外または過大な入力は、いかなる構築作業が走る前に境界で拒否し、コンカレンシーを制限して、高トラフィックがピークメモリーを増幅して OOM 障害にしないようにしてください。
- フォントとライセンスを公開成果物の外に保ってください。 埋め込むライセンスを持つフォントのみを同梱し、Premium のライセンスファイルを公開プッシュされるイメージやレイヤーに決して焼き込まないでください——代わりに、環境値またはシークレットマネージャー経由でランタイムに供給してください。
- 最小権限。 関数/サービスには、必要な IAM 権限のみ(たとえば 1 つの出力バケットへの書き込みアクセス)を与え、Docker ガイドが示すようにコンテナを非特権で実行してください。
このガイドは規範的な標準の主張を行いません。プラットフォームの事実は nextpdf/core パッケージから直接読み取られます。php: >=8.4 <9.0 制約と、必須拡張 ext-mbstring、ext-intl、ext-gd、ext-openssl、ext-zlib、ext-curl。標準の Bref PHP-8.4 ランタイムレイヤーは 6 つすべてを同梱します。公式の php:8.4 イメージは openssl、curl、zlib を提供しますが、mbstring、gd、intl は docker-php-ext-install でイメージビルドにインストールして有効化しなければなりません(Docker ページを参照)。出力呼び出しは実在するコアサーフェス NextPDF\Core\Document::getPdfData(): string です(そのディスク兄弟は save(string $path): void)。フォントは NextPDF\Typography\FontRegistry を通じて登録されます——そのディレクトリコンストラクター引数 / addFontDirectory()、コールドスタートパターン用の warmup(array $fontFiles) と lock()——NextPDF\Core\DocumentFactory::create() 経由で配線されます。NEXTPDF_FONTS_PATH は nextpdf/laravel パッケージの fonts_path 設定キー(env('NEXTPDF_FONTS_PATH', resource_path('fonts')))であり、nextpdf/core が読む変数ではありません。nextpdf CLI の doctor コマンドは、パッケージで "bin": ["bin/nextpdf"] として宣言され、消費側アプリでは vendor/bin/nextpdf にインストールされます。Bref のランタイム名と AWS Lambda / Cloud Run / App Runner の挙動は、それらベンダーの文書化された機能です。
- NextPDF アプリケーションをコンテナ化する: Cloud Run / App Runner のターゲットに使う本番イメージ。
- 本番でネイティブエンジン向けにフォントをプロビジョニングする: このページが依拠するフォントファイルの命名、レジストリ API、ウォームアップとロックのパターン。
- 大きな生成済み PDF を HTTP レスポンスとしてストリームする: Cloud Run / App Runner で構築済みドキュメントを HTTP 越しに返すためのメモリーモデル。
- Cloudflare でエッジレンダリングする: インプロセス関数が適切なランタイムでない場合。