¿Por qué autoalojar una API de generación de PDF?
Los servicios de PDF en modo SaaS (Puppeteer Cloud, PDFShift, DocRaptor) cobran por documento, por página o por volumen de llamadas a la API. En cuanto su aplicación genera facturas, informes o contratos a escala, el costo aumenta de forma lineal con su uso. Gotenberg los sustituye por un costo mensual fijo de VPS, sea cual sea el número de PDF generados. También preserva la confidencialidad de sus documentos: nada sale de su infraestructura durante la conversión.
¿Gotenberg, WeasyPrint, Stirling-PDF o Puppeteer?
Desplace la tabla
| Herramienta | Formatos fuente | RAM mínima | API REST | Licencia |
|---|---|---|---|---|
| Gotenberg 8.x | HTML, URL, DOCX/ODT/XLSX, Markdown, imágenes | 2 GB (Chromium) | Sí — multipart/form-data | MIT |
| WeasyPrint | HTML + CSS (sin JS) | 256 MB | No — lib Python/CLI | BSD |
| Stirling-PDF | PDF (edición, OCR, fusión) | 512 MB | Sí — JSON/multipart | MIT (AGPLv3 para Pro) |
| Puppeteer solo | HTML + URL via Chrome headless | 1,5 GB | No — lib Node.js | Apache 2.0 |
Lo que Gotenberg puede convertir
- HTML a PDF mediante una instancia completa de Chromium — se respetan CSS, fuentes web, JavaScript y las media queries de impresión.
- Cualquier URL a PDF — archive vistas de paneles de control, genere informes semanales o capture recibos de pago.
- Documentos LibreOffice (DOCX, XLSX, PPTX, ODT, ODS, ODP y más de 20 formatos) a PDF sin Microsoft Office.
- Markdown a PDF por la ruta Chromium, con CSS configurable.
- Varios PDF fusionados en un único archivo con una sola llamada a la API.
- Imágenes (PNG, JPEG, WebP, TIFF) convertidas a PDF.
Requisitos previos
Un VPS con al menos 1 vCPU y 2 GB de RAM (Chromium solo consume entre 1,2 y 1,5 GB durante el renderizado; 1 GB de RAM es insuficiente en cuanto hay una conversión en curso). Ubuntu 22.04 o 24.04 con Docker y Docker Compose v2 instalados. Gotenberg escucha en el puerto 3000 por defecto. No hace falta ningún dominio si llama a la API desde el mismo servidor — http://127.0.0.1:3000 es suficiente. Para exponerlo a otros servicios se recomienda un reverse proxy nginx (véase más abajo).
Desplegar Gotenberg paso a paso
Crear el archivo compose
Conéctese por SSH a su VPS y cree un directorio:
mkdir gotenberg && cd gotenbergCree
docker-compose.yml:services: gotenberg: image: gotenberg/gotenberg:8 restart: unless-stopped ports: - "127.0.0.1:3000:3000" command: - gotenberg - --chromium-disable-javascript=false - --api-timeout=30s - --chromium-max-queue-size=10La vinculación a
127.0.0.1impide el acceso directo desde Internet. En 2026 la versión estable es 8.x (8.9.1 y superiores) — la imagen:8sigue la última 8.x.Arrancar el contenedor
Ejecute
docker compose up -d. Gotenberg descarga la imagen (~1,5 GB, incluye Chromium y LibreOffice) e inicia el servidor HTTP. Compruebe el endpoint de salud:curl http://127.0.0.1:3000/healthUna respuesta
{"status":"up"}confirma que el servicio está listo.Generar su primer PDF a partir de HTML
Cree un archivo HTML mínimo (
factura.html) y conviértalo:curl -s --request POST http://127.0.0.1:3000/forms/chromium/convert/html \ --form '[email protected]' \ -o factura.pdfLa respuesta es el PDF en flujo binario — rediríjalo a un archivo, a un objeto de respuesta o a una subida a S3.
Convertir un documento Word a PDF
Suba un archivo DOCX a la ruta LibreOffice:
curl -s --request POST http://127.0.0.1:3000/forms/libreoffice/convert \ --form '[email protected]' \ -o contrat.pdfGotenberg utiliza el motor LibreOffice integrado para la conversión — no se requiere ninguna licencia de Microsoft Office.
Añadir una autenticación (recomendado)
Gotenberg no trae autenticación integrada. El enfoque recomendado: instale nginx, cree un reverse proxy de
https://gotenberg.su-dominio.comhaciahttp://127.0.0.1:3000y añada una autenticación HTTP basic. No lo exponga nunca sin autenticación a la Internet pública, ya que puede utilizarse para renderizar URL arbitrarias.
Rutas y formatos de la API
Gotenberg expone dos familias de rutas REST, todas en POST multipart/form-data.
Rutas Chromium — renderizado mediante navegador headless:
- POST /forms/chromium/convert/html — conversión desde un archivo HTML local (campo index.html).
- POST /forms/chromium/convert/url — conversión desde una URL pública (campo url); Chromium carga la página completa, JavaScript incluido.
- POST /forms/chromium/convert/markdown — conversión Markdown con hoja de estilos opcional.
Rutas LibreOffice — conversión de documentos ofimáticos:
- POST /forms/libreoffice/convert — acepta DOCX, XLSX, PPTX, ODT, ODS, ODP y más de 20 formatos; varios archivos fusionados en un único PDF si envía merge=true.
Rutas PDF — manipulación de PDF existentes:
- POST /forms/pdfengines/merge — fusiona varios archivos PDF en uno.
- POST /forms/pdfengines/convert — convierte a PDF/A (archivo a largo plazo).
Webhook: en lugar de recibir el PDF como respuesta síncrona, pase una cabecera Gotenberg-Webhook-Url — Gotenberg enviará entonces el PDF mediante POST a su URL en cuanto finalice la conversión.
Reverse proxy nginx
Para exponer Gotenberg a otros servicios o llamarlo desde un dominio dedicado, añada un bloque location en su configuración nginx:
server {
listen 443 ssl;
server_name gotenberg.su-dominio.com;
ssl_certificate /etc/letsencrypt/live/gotenberg.su-dominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gotenberg.su-dominio.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
client_max_body_size 50M;
auth_basic "Gotenberg API";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}Si Gotenberg corre en el mismo servidor que su backend, prefiera la llamada directa a http://127.0.0.1:3000 — menor latencia y no es necesario exponer el servicio en un dominio.
Integración avanzada: PHP, Python, Node.js
Gotenberg es independiente del lenguaje — cualquier cliente HTTP capaz de enviar multipart/form-data funciona.
PHP / Laravel — use el paquete oficial gotenberg-php:
use Gotenberg\Gotenberg;
use Gotenberg\Stream;
$request = Gotenberg::chromium('http://127.0.0.1:3000')
->pdf()
->html(Stream::path('/ruta/a/factura.html'));
$response = $client->sendRequest($request);
file_put_contents('factura.pdf', $response->getBody());Python — con la librería gotenberg-client:
from gotenberg_client import GotenbergClient
with GotenbergClient('http://127.0.0.1:3000') as client:
with client.chromium.html_to_pdf() as route:
route.index(Path('factura.html'))
response = route.run()
Path('factura.pdf').write_bytes(response.content)Node.js — con fetch nativo (Node 18+):
const form = new FormData();
form.append('index.html', new Blob([htmlContent], { type: 'text/html' }), 'index.html');
const res = await fetch('http://127.0.0.1:3000/forms/chromium/convert/html', {
method: 'POST',
body: form,
});
const pdf = Buffer.from(await res.arrayBuffer());
fs.writeFileSync('factura.pdf', pdf);Para Node.js, el paquete chromiumly envuelve la API completa si prefiere evitar el FormData manual.
Configuración avanzada
Los parámetros se pasan como argumentos al comando gotenberg en su docker-compose.yml (o mediante variables de entorno con el prefijo GOTENBERG_).
Tiempo límite: --api-timeout=30s — duración máxima de una conversión. Auméntelo (p. ej. 60s) para documentos DOCX largos o páginas con JavaScript pesado.
Concurrencia: --chromium-max-queue-size=10 — número máximo de conversiones Chromium en cola. Superado este límite, Gotenberg responde 503 en lugar de acumular en memoria.
Logs: Gotenberg registra en JSON en stdout — docker compose logs gotenberg le da el detalle de cada conversión.
Deshabilitar LibreOffice (si solo necesita Chromium, ahorra RAM):
command:
- gotenberg
- --libreoffice-disable-listener=truePara arquitecturas de alto volumen, lance varias réplicas de Gotenberg detrás de un balanceador de carga nginx (upstream): cada instancia mantiene sus propios workers de Chromium y escala horizontalmente sin estado compartido.
Resolución de errores comunes
Timeout de Chromium — context deadline exceeded: la conversión HTML supera el timeout configurado. Causas frecuentes: JavaScript lento, recursos externos (CDN, Google Fonts) inaccesibles desde el servidor, o DOM demasiado grande. Solución: aumente --api-timeout, aloje sus assets localmente y adjúntelos como campos multipart.
Puerto 3000 ya en uso: otra aplicación escucha en 3000. Cambie el mapeo en docker-compose.yml: "127.0.0.1:3001:3000" y actualice la URL en sus llamadas.
Archivo demasiado grande — 413 Request Entity Too Large: nginx corta la petición antes de llegar a Gotenberg. Aumente client_max_body_size en el bloque nginx (p. ej. 100M para archivos XLSX grandes).
Sin memoria — OOMKilled: Docker mata el contenedor cuando Chromium supera la RAM disponible. Verifique con docker inspect gotenberg | grep OOMKilled. Remedios: añada RAM al VPS, limite la concurrencia (--chromium-max-queue-size=3), o deshabilite JavaScript si su HTML no lo requiere (--chromium-disable-javascript=true).
Error de LibreOffice — archivo corrupto o formato no reconocido: algunos DOCX generados por herramientas de terceros no son conformes con el estándar OOXML. Verifique primero que el archivo se abre sin errores en LibreOffice Desktop. La cabecera del log de Gotenberg indica el mensaje exacto de LibreOffice (docker compose logs gotenberg --tail 50).
Integrarlo con su aplicación
Gotenberg es independiente del lenguaje: cualquier cliente HTTP funciona. Existen bibliotecas para PHP (gotenberg-php), Node.js (chromiumly), Python (gotenberg-client), Go y otros. Para Laravel, un paquete oficial encapsula la API. En Node.js, puede llamarla con axios o con la API fetch nativa enviando un objeto FormData con el contenido HTML o el archivo. Consulte la sección de integración avanzada de más arriba para ejemplos de código listos para usar en cada lenguaje.