Execute o NextPDF em plataformas serverless
Visão geral
Seção intitulada “Visão geral”O mecanismo core nativo e in-process do NextPDF é uma carga de trabalho serverless
quase ideal. Ele é PHP puro executando dentro do seu processo —
composer require nextpdf/core, construa um documento, obtenha os bytes. Não há
binário externo para iniciar, nenhum navegador headless, nenhum daemon para manter
vivo e nenhum socket para um serviço sidecar. Uma função que constrói um PDF inicia
a frio, executa seu PHP, retorna os bytes e termina. Isso se mapeia de forma limpa
para o AWS Lambda (por meio do runtime Bref), o Google Cloud Run e o AWS
App Runner.
Esta página aborda a implantação desse mecanismo nativo nesses três runtimes e o pequeno conjunto de restrições reais que eles impõem:
- o sistema de arquivos do runtime não é durável: o Lambda garante apenas um
/tmpgravável, enquanto os runtimes de contêiner (Cloud Run, App Runner) têm um sistema de arquivos efêmero, com escopo de contêiner — de qualquer forma, as fontes devem viajar dentro do pacote de implantação ou imagem e ser registradas no PHP (o mecanismo não lê nenhuma variável de ambiente de caminho de fontes); - os cold starts pagam pelo autoloading e por qualquer aquecimento de fontes,
então aqueça o
FontRegistryuma vez por contêiner, não por invocação; - o tamanho do pacote, a memória e o timeout devem ser dimensionados para o build, não para uma requisição trivial.
Esta página é apenas para o mecanismo nativo. A ponte Chrome
(writeHtmlChrome por meio do pacote sugerido nextpdf/artisan) é uma história
diferente e mais pesada: ela aciona um Chromium headless por meio do
symfony/process, que um zip Lambda padrão ou um contêiner enxuto não contém.
Executar o Chromium no Lambda significa uma layer personalizada com o navegador e
suas bibliotecas compartilhadas, pacotes muito maiores e cold starts muito mais
longos — fora do escopo aqui. O mecanismo puro não precisa de nada disso.
Antes de começar, confirme se estas peças estão no lugar:
- Sua aplicação tem um
composer.jsone umcomposer.lockcommitados, comnextpdf/corecomo dependência. - Você tem os arquivos de fonte que pretende incorporar, e você está licenciado para incorporá-los.
- Você tem o toolchain do seu alvo — a CLI do Bref e o framework
serverlesspara Lambda, ou um build de contêiner para Cloud Run / App Runner.
Por que o mecanismo nativo se encaixa em serverless
Seção intitulada “Por que o mecanismo nativo se encaixa em serverless”Lido diretamente do pacote, o nextpdf/core requer php: >=8.4 <9.0 e um pequeno
conjunto de extensões PHP — ext-mbstring, ext-intl, ext-gd, ext-openssl,
ext-zlib e ext-curl. As layers PHP padrão do Bref empacotam todas elas. As
imagens de contêiner oficiais php:8.4 fornecem openssl, curl e zlib de
fábrica, mas mbstring, gd e intl não são empacotadas — elas exigem
instalar dependências do sistema e habilitar as extensões com
docker-php-ext-install (consulte o
guia de implantação com Docker).
No Bref não há nada exótico para compilar; no caminho de contêiner você habilita
essas três extensões no build da imagem para o mecanismo puro.
O que torna o encaixe limpo é o que o mecanismo não faz:
- Nenhum subprocesso para o caminho core. Construir um documento e chamar
getPdfData()é PHP in-process de ponta a ponta. A dependênciasymfony/processexiste para a ponte Chrome opcional, não para a renderização nativa — a geração nativa de PDF nunca inicia um processo. - Nenhum estado persistente. Cada invocação constrói um documento novo e retorna bytes. Nada precisa sobreviver entre requisições, exceto o contêiner aquecido, que você explora para o aquecimento de fontes (abaixo) mas nunca usa como base para correção.
- Nenhum diretório de trabalho gravável necessário. O mecanismo constrói o PDF
em memória e o retorna como uma string; ele toca o disco apenas se você chamar
save(). Em serverless você não chama — você retorna os bytes — então a falta de um sistema de arquivos durável nunca afeta o caminho de build.
A única restrição rígida: nenhum sistema de arquivos gravável durável
Seção intitulada “A única restrição rígida: nenhum sistema de arquivos gravável durável”O sistema de arquivos de implantação não é durável, mas o modelo difere por
runtime. O AWS Lambda garante apenas um /tmp gravável (512 MB por padrão,
configurável até 10 GB); o resto do sistema de arquivos da função é somente
leitura. Os runtimes de contêiner (Cloud Run, App Runner) têm um sistema de
arquivos gravável efêmero, com escopo de contêiner em vez de um modelo apenas
/tmp — mas qualquer coisa escrita ali é perdida quando o contêiner é reciclado,
então é espaço de rascunho, não armazenamento. Em todos os casos, prefira /tmp
ou um volume configurado para staging, e nunca use escritas no caminho da imagem da
aplicação como armazenamento durável. Duas consequências decorrem disso.
Nunca chame save() esperando uma saída durável. NextPDF\Core\Document expõe
tanto save(string $path): void quanto getPdfData(): string. Em serverless você
usa getPdfData() e retorna ou faz upload dos bytes — não trate uma escrita no
diretório da aplicação como armazenamento persistente. Se você precisa fazer
staging de um arquivo (por exemplo, para fazer upload multipart para armazenamento
de objetos), escreva sob /tmp (ou um volume configurado) e faça a limpeza,
lembrando que em um contêiner aquecido esse espaço de rascunho persiste entre
invocações e conta para o limite de tamanho dele.
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 insteadNão instale fontes do SO em tempo de execução, e não confie na descoberta
automática de fontes; empacote seus arquivos de fonte para produção. No Lambda o
sistema de arquivos somente leitura bloqueia apt-get install fonts-* por
completo; em um runtime de contêiner qualquer instalação em tempo de execução cai
em um sistema de arquivos efêmero e é perdida na próxima reciclagem. E isso não
ajudaria de qualquer forma, porque o mecanismo nativo não lê fontes do
SO/fontconfig — ele resolve fontes apenas a partir de arquivos que você registra.
Então, para produção, os arquivos de fonte devem ir dentro do artefato de
implantação. Se você deliberadamente buscar arquivos de fonte para /tmp ou um
volume configurado, você deve registrá-los explicitamente no registro de fontes e
aceitar o custo adicional de cold start e de confiabilidade — não é um padrão de
produção recomendado.
Empacote e registre fontes no pacote ou na imagem
Seção intitulada “Empacote e registre fontes no pacote ou na imagem”O mecanismo nativo resolve fontes a partir de arquivos de fonte por meio do
NextPDF\Typography\FontRegistry, não a partir do fontconfig ou de fontes
instaladas pelo SO. Em serverless isso é inegociável: não há sistema de arquivos
persistente onde colocar fontes após a implantação, então elas vão dentro do
pacote (um zip ou layer Lambda) ou dentro da imagem (Cloud Run / App Runner).
Empacote seus arquivos .ttf / .otf / .ttc sob um diretório no seu projeto —
resources/fonts/ é a convenção — para que sejam incluídos no artefato. Em
seguida, registre esse diretório no PHP. O mecanismo não lê nenhuma variável de
ambiente de caminho de fontes: NEXTPDF_FONTS_PATH é o valor padrão da chave de
configuração fonts_path do pacote nextpdf/laravel
(env('NEXTPDF_FONTS_PATH', resource_path('fonts'))) e é consumido apenas por
essa integração de framework, não pelo nextpdf/core. Uma função pura deve
construir o registro com o diretório empacotado:
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();Essa é toda a preocupação serverless com fontes. As regras de nomeação de arquivos, a API completa do registro e o tratamento de sistema de arquivos não durável vivem na página dedicada — não os duplique aqui. Leia Provisione fontes para o mecanismo nativo em produção para o padrão completo, e registre o mesmo diretório que você empacotou. O guia de implantação com Docker cobre o empacotamento equivalente do lado da imagem para o caso Cloud Run / App Runner.
Cold starts: aqueça o FontRegistry uma vez por contêiner
Seção intitulada “Cold starts: aqueça o FontRegistry uma vez por contêiner”Um cold start paga pelo bootstrap do PHP, pelo autoloader otimizado do Composer e por qualquer análise (parsing) de fontes que o primeiro build dispare. Você não pode evitar o bootstrap, mas pode tirar o trabalho de fontes do caminho quente e reutilizá-lo entre invocações aquecidas.
Construa o FontRegistry e o DocumentFactory uma vez, fora do handler, para
que vivam pela vida do contêiner e sejam reutilizados em toda invocação aquecida.
Opcionalmente chame warmup() com os arquivos de fonte que você sabe que vai usar,
para que sejam analisados durante a inicialização em vez de na primeira
renderização, e então use lock() no registro para que seu estado analisado seja
congelado e nenhuma mutação por invocação possa entrar em condição de corrida:
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();};Chame warmup() antes de lock() — o registro fica congelado uma vez
travado, então um warmup depois disso levanta um erro de configuração. Trate uma
fonte que falha ao carregar no warmup como um erro em tempo de implantação, não
um detalhe de tempo de execução: valide que todo caminho de fonte que você
pretende aquecer realmente existe e é analisável na inicialização, e reprove a
implantação (ou seu health check) se algum não for, em vez de deixar um caminho com
erro de digitação aparecer mais tarde como glifos ausentes. Mantenha a lista de
warmup nas fontes que uma invocação típica precisa; aquecer uma família grande que
você raramente usa apenas alonga cada cold start.
Uma função Bref no AWS Lambda
Seção intitulada “Uma função Bref no AWS Lambda”O Bref fornece o runtime PHP para o Lambda como uma layer
publicada e um plugin serverless.yml. O runtime php-84 já vem com as extensões
que o nextpdf/core precisa, então você implanta seu código e fontes e aponta uma
função para um handler. Um serverless.yml mínimo:
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.O handler constrói o documento com a factory aquecida, com escopo de contêiner, e
retorna os bytes. Para uma API HTTP, retorne-os codificados em base64 com o content
type application/pdf para que o API Gateway trate o corpo como binário; para um
gatilho de invoke ou fila, faça upload dos bytes para o armazenamento de objetos e
retorne a chave:
<?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), ];};Verifique se o pacote contém um ambiente saudável antes de direcionar tráfego para
ele. O nextpdf/core traz uma CLI instalada em vendor/bin/nextpdf cujo comando
doctor relata exatamente as extensões que o mecanismo precisa. Execute-o uma vez
contra a mesma imagem de runtime ou layer para confirmar que o PHP 8.4 e toda
extensão necessária estão presentes.
Cloud Run e App Runner
Seção intitulada “Cloud Run e App Runner”O Cloud Run e o App Runner executam um contêiner em vez de uma função zipada,
então o build é a imagem Docker de
Containerize a NextPDF application,
não um pacote Bref. As restrições do mecanismo nativo são idênticas: empacote as
fontes na imagem, registre o diretório empacotado no PHP, execute sem privilégios e
trate o sistema de arquivos como não durável. Diferentemente do modelo apenas
/tmp do Lambda, um contêiner Cloud Run / App Runner tem um sistema de arquivos
gravável efêmero, com escopo de contêiner — mas ele é reiniciado a cada reciclagem,
então use /tmp (um tmpfs no Cloud Run) ou um volume configurado para rascunho e
nunca use escritas no caminho da imagem da aplicação como armazenamento durável.
As diferenças em relação ao Lambda são operacionais, não estruturais:
- O contêiner pode permanecer aquecido entre requisições sob uma configuração
de concorrência, então o warmup do
FontRegistry/DocumentFactorycom escopo de contêiner acima compensa em muitas requisições, não apenas na próxima invocação. - Você serve por HTTP (uma SAPI FPM ou de servidor PHP embutido) em vez de um evento de invoke, então você retorna os bytes pela resposta do seu framework. Para um documento grande, retorne-os como uma resposta em streaming — consulte Faça streaming de um PDF grande gerado como resposta HTTP.
- O timeout e a memória da requisição são definidos no serviço (timeout / memória do serviço Cloud Run; configuração de instância do App Runner) em vez de por função.
Todo o resto — o conjunto de extensões, o registro de fontes, a chamada de saída
getPdfData() — é o mesmo código do handler do Lambda.
Dimensionamento: pacote, memória e timeout
Seção intitulada “Dimensionamento: pacote, memória e timeout”- Tamanho do pacote e da imagem. O artefato carrega
vendor/(apenas produção — instale com--no-dev) e suas fontes empacotadas. As fontes dominam: uma família CJK completa tem dezenas de megabytes. Envie apenas as fontes que você de fato renderiza para manter o pacote Lambda abaixo dos seus limites e a imagem pequena, o que também encurta os cold starts. A família Liberation empacotada (resources/fonts/liberation/) é pequena e cobre a substituição de Helvetica metric-compatible. - Memória.
getPdfData()constrói o documento inteiro em memória e o retorna como uma única string, então o pico de memória é aproximadamente o tamanho de um PDF finalizado mais o working set do build. Dimensione a memória da função/contêiner para o maior documento que você gera, não para uma média. No Lambda, a memória também escala a CPU, então mais memória muitas vezes significa um build mais rápido e uma execução mais barata apesar da taxa por milissegundo mais alta — meça os dois. Um documento de poucas páginas é confortável em 512–1024 MB; documentos pesados em imagens ou com muitas páginas precisam de mais. - Timeout. O build, não a transferência, domina o orçamento da requisição. Defina o timeout da função acima do tempo de build do pior caso com margem. Se um documento for grande o suficiente para arriscar um timeout, mova a geração para um gatilho assíncrono (um Lambda baseado em fila ou um job do Cloud Run) que escreve o resultado no armazenamento de objetos em vez de bloquear uma requisição síncrona.
- Tamanho do
/tmp. Se você fizer staging de qualquer coisa sob/tmp, considere seu limite de tamanho e lembre que ele persiste entre invocações aquecidas — faça a limpeza, ou um contêiner de longa vida o preenche lentamente.
Casos extremos e armadilhas
Seção intitulada “Casos extremos e armadilhas”- Sem
save()durável para o diretório da aplicação. O sistema de arquivos de implantação não é durável — o diretório da aplicação do Lambda é somente leitura (apenas/tmpaceita escritas), e um sistema de arquivos de contêiner Cloud Run / App Runner é gravável mas efêmero. UsegetPdfData()e retorne/faça upload dos bytes; faça staging sob/tmpou um volume configurado se for necessário. - Não confie na descoberta automática de fontes. Não instale fontes do SO em
tempo de execução, e não confie na descoberta automática de fontes; empacote seus
arquivos de fonte para produção. O mecanismo nativo não lê fontes do
SO/fontconfig — ele resolve apenas arquivos que você registra. Se você
deliberadamente buscar arquivos de fonte para
/tmpou um volume configurado, você deve registrá-los explicitamente no registro de fontes e aceitar o custo adicional de cold start e de confiabilidade. Empacote e registre os arquivos. Consulte a página de fontes linkada acima. NEXTPDF_FONTS_PATHnão faz nada para o mecanismo puro. Ele é o padrão de configuração donextpdf/laravel, não uma variável que onextpdf/corelê. Um handler Bref puro que define apenas essa variável não registra nenhuma fonte e renderiza tofu.- A ponte Chrome não se encaixa em uma função padrão.
writeHtmlChromeprecisa de um Chromium headless e do caminho de subprocessosymfony/process. Colocar o Chromium no Lambda exige uma layer personalizada com o navegador e suas bibliotecas, pacotes muito maiores e cold starts longos. O mecanismo nativo e owriteHtmlnão precisam de nada disso — prefira-os em serverless. - O custo de cold start é autoload mais parse de fontes. Use
--optimize-autoloaderna instalação de produção e aqueça o registro uma vez por contêiner. Não aqueça fontes que você raramente usa. - O API Gateway precisa de tratamento binário. Retorne
isBase64Encoded: truecomContent-Type: application/pdf, e configure a API para tratarapplication/pdfcomo um tipo de mídia binário, ou o cliente recebe bytes corrompidos. - Premium e ionCube são uma preocupação de artefato mais pesada. Builds NextPDF Pro / Enterprise codificados com ionCube precisam do ionCube Loader correspondente ao build exato de PHP no runtime, que uma layer Bref padrão não inclui. Isso está fora do escopo de uma implantação serverless core.
Notas de segurança
Seção intitulada “Notas de segurança”- Não envie dependências de desenvolvimento. Instale com
--no-devpara que o ferramental de teste e análise nunca entre no pacote ou imagem da função. - Valide a entrada antes de construir. Um build de PDF guiado por entrada de requisição é um vetor de exaustão de memória; rejeite entradas fora do intervalo ou grandes demais na fronteira antes que qualquer trabalho de build seja executado, e limite a concorrência para que tráfego alto não multiplique o pico de memória em uma falha de out-of-memory.
- Mantenha fontes e licenças fora de artefatos públicos. Empacote apenas fontes que você está licenciado para incorporar, e nunca embuta um arquivo de licença premium em uma imagem ou layer enviada publicamente — forneça-o em tempo de execução por meio de um valor de ambiente ou gerenciador de segredos.
- Privilégio mínimo. Dê à função/serviço apenas as permissões IAM que ela precisa (por exemplo, acesso de escrita ao único bucket de saída), e execute o contêiner sem privilégios, como o guia do Docker mostra.
Conformidade
Seção intitulada “Conformidade”Este guia não faz nenhuma declaração normativa de padrões. Os fatos da plataforma
são lidos diretamente do pacote nextpdf/core: a restrição php: >=8.4 <9.0 e as
extensões necessárias ext-mbstring, ext-intl, ext-gd, ext-openssl,
ext-zlib e ext-curl. A layer de runtime PHP-8.4 padrão do Bref empacota todas
as seis; a imagem oficial php:8.4 fornece openssl, curl e zlib, mas
mbstring, gd e intl devem ser instaladas e habilitadas no build da imagem com
docker-php-ext-install (consulte a página do Docker). A chamada de saída é a
superfície core real
NextPDF\Core\Document::getPdfData(): string (sua irmã de disco é
save(string $path): void). As fontes são registradas por meio do
NextPDF\Typography\FontRegistry — seu argumento de construtor de diretório /
addFontDirectory(), com warmup(array $fontFiles) e lock() para o padrão de
cold start — conectado via NextPDF\Core\DocumentFactory::create().
NEXTPDF_FONTS_PATH é a chave de configuração fonts_path do pacote
nextpdf/laravel (env('NEXTPDF_FONTS_PATH', resource_path('fonts'))), não uma
variável que o nextpdf/core lê. O comando doctor da CLI nextpdf é declarado
como "bin": ["bin/nextpdf"] no pacote e instalado em vendor/bin/nextpdf em uma
aplicação consumidora. Os nomes de runtime do Bref e os comportamentos de AWS
Lambda / Cloud Run / App Runner são recursos documentados desses fornecedores.
Veja também
Seção intitulada “Veja também”- Containerize a NextPDF application: a imagem de produção usada para os alvos Cloud Run / App Runner.
- Provisione fontes para o mecanismo nativo em produção: a nomeação de arquivos de fonte, a API do registro e o padrão de warmup-e-lock em que esta página se apoia.
- Faça streaming de um PDF grande gerado como resposta HTTP: o modelo de memória para retornar um documento construído por HTTP no Cloud Run / App Runner.
- Renderize na edge com Cloudflare: quando uma função in-process não é o runtime certo.