Ir al contenido

Configuración del renderer de Chrome para NextPDF Artisan

El puente lanza y controla un proceso local de Chrome/Chromium mediante chrome-php/chrome. En esta página se configura ese runtime para que los renders se completen correctamente, incluidas las decisiones sobre contenedores y sandbox.

BrowserPool construye una chrome-php/chromeBrowserFactory (opcionalmente con una ruta de binario explícita) y lanza Chrome con un conjunto fijo de flags: headless: true, keepAlive: true, windowSize: [1200, 800], sendSyncDefaultTimeout: renderTimeout * 1000, y los flags personalizados que se enumeran en la página /integrations/artisan/configuration/. Después, el puente controla el proceso lanzado mediante el Chrome DevTools Protocol. No se conecta a un Chrome ejecutándose por separado a través de un puerto de depuración remota, por lo que no hay ningún endpoint de red que exponer ni autenticar. Chrome se ejecuta como proceso hijo del worker de PHP. El test tests/Unit/Artisan/BrowserPoolTest.php::getBrowserCreatesAndReusesInstanceWithExpectedOptions verifica exactamente estas opciones de lanzamiento.

Instalar una build de Chrome o Chromium que el usuario worker pueda ejecutar:

Ventana de terminal
# Debian / Ubuntu
apt-get install -y chromium
# RHEL / Fedora
dnf install -y chromium
# Alpine (containers)
apk add --no-cache chromium nss freetype harfbuzz ttf-freefont

Verificar que se ejecute en modo headless como el usuario worker:

Ventana de terminal
chromium --headless --dump-dom about:blank

Un código de salida 0 con un DOM vacío indica que el binario y sus bibliotecas compartidas están presentes. Una salida distinta de cero es el mismo fallo que el puente expone como un ChromeRenderException. Debe corregirse aquí primero.

La detección automática (el comportamiento predeterminado de chrome-php/chrome) funciona cuando existe un binario en una ruta estándar. Para obtener determinismo en producción, conviene fijarlo explícitamente:

$config = new ChromeRendererConfig(
chromeBinaryPath: '/usr/bin/chromium',
);

o con configuración en array:

$config = ChromeRendererConfig::fromArray([
'chrome_binary' => '/usr/bin/chromium',
]);

Aprovisionamiento en contenedores y decisión del sandbox

Sección titulada «Aprovisionamiento en contenedores y decisión del sandbox»

En un contenedor, el sandbox del sistema operativo de Chrome a menudo no puede inicializarse como root / PID 1 sin capabilities de kernel adicionales. Hay dos caminos:

  1. Mantener el sandbox (preferido). Ejecutar el worker como un usuario no root y otorgar al contenedor las capabilities que necesita el sandbox de Chrome (habitualmente SYS_ADMIN, o un perfil de seccomp que permita crear espacios de nombres de usuario). Esto mantiene intacto el aislamiento de procesos de Chrome.
  2. Desactivar el sandbox. Establecer no_sandbox: true. Chrome se lanza con --no-sandbox. Esto elimina el sandbox de aislamiento de procesos de Chrome: una reducción real de la contención, no un flag cosmético. Usarlo solo donde el sandbox no pueda habilitarse, ejecutar Chrome como un usuario no root dentro de un contenedor restringido y tratar el despliegue como si exigiera un nivel de confianza mayor en la entrada. Las barreras de red del puente (CSP + bloqueo de CDP) siguen vigentes en cualquier caso, pero no sustituyen al aislamiento de procesos. Esto se alinea con la guía de mínimo privilegio y aislamiento de OWASP ASVS para renderizar contenido no confiable.

La declaración completa de la frontera —lo que el sandbox protege y lo que no— está en la página /integrations/artisan/security-and-operations/. Esta página no afirma que desactivar el sandbox sea seguro.

FROM php:8.4-cli
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -u 10001 worker
USER worker
ENV CHROME_BINARY=/usr/bin/chromium
# Set CHROME_NO_SANDBOX=1 only if the sandbox cannot be enabled in your runtime.

Ejecutar el worker como worker (uid 10001), no como root. El puente ya aplica el flag --disable-dev-shm-usage, que evita el crash por /dev/shm pequeño, habitual en contenedores sin más ajustes.

El puente bloquea las descargas de fuentes remotas (--disable-remote-fonts y CSP). Instalar las fuentes necesarias en la capa del sistema operativo, o incrustarlas mediante URI data: en reglas @font-face dentro de defaultCss o del HTML. La salida CJK requiere un paquete de fuentes CJK (por ejemplo fonts-noto-cjk) instalado en la imagen.

Usar esta sonda independiente para probar la ruta completa del puente sin la aplicación host:

chrome-health.php
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;
use NextPDF\Artisan\ChromeRendererConfig;
require __DIR__ . '/vendor/autoload.php';
$renderer = new ChromeHtmlRenderer(
ChromeRendererConfig::fromArray([
'chrome_binary' => getenv('CHROME_BINARY') ?: null,
'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'),
]),
);
$result = $renderer->render('<p>ok</p>', 200.0, 0.0);
fwrite(STDOUT, strlen($result->getPdfData()) > 0 ? "CHROME_OK\n" : "CHROME_EMPTY\n");
$renderer->close();

CHROME_OK confirma el lanzamiento, el render y el import. Una excepción lanzada identifica el fallo preciso. Consultarla en la página /integrations/artisan/troubleshooting/. Conectarla como comprobación de readiness en despliegues orquestados.

  • Ejecutar Chrome como un usuario no root dedicado.
  • Aplicar un límite de memoria del host; el puente acota el crecimiento con un reinicio cada 100 renders, pero sigue siendo necesario un techo del host.
  • Combinar render_timeout con un presupuesto de solicitud aguas arriba en cualquier ruta alcanzable por entrada no confiable.
  • No exponer un puerto de depuración remota de Chrome. El puente no usa ninguno y un puerto CDP abierto es un canal de control sin autenticar.
SíntomaCausa probableDónde mirar
ChromeNotAvailableExceptionchrome-php/chrome no instalado/integrations/artisan/install/
ChromeRenderException en el primer renderBinario ausente / el sandbox no puede inicializarseEsta página; /integrations/artisan/troubleshooting/
PDF vacíoSin caja visible / crash de Chrome/integrations/artisan/troubleshooting/
Imágenes remotas en blancoRed bloqueada por diseño/integrations/artisan/security-and-operations/
Pico de latencia periódicoReinicio cada 100 renders/integrations/artisan/production-usage/
  • /integrations/artisan/install/
  • /integrations/artisan/configuration/
  • /integrations/artisan/security-and-operations/
  • /integrations/artisan/troubleshooting/
  • /integrations/artisan/production-usage/