Self-Hosting Your Document Conversion Pipeline
Most document conversion workflows depend on third-party APIs that charge per request, enforce file size limits, or quietly log your uploaded content. Gotenberg solves that problem by running as a local Docker container that converts HTML, Markdown, Office documents, and URLs into PDFs – entirely on your own infrastructure. No rate limits, no data leaving your server, no monthly invoices from a SaaS provider.
The setup process is straightforward enough that developers can have a working instance in under thirty minutes, but there are enough configuration choices – port exposure, Chromium flags, timeout settings, multi-instance scaling – that rushing through it causes problems later. This guide covers the full setup from Docker installation through testing your first conversion request.

Prerequisites and Environment Setup
Gotenberg runs as a Docker image, so the only hard requirement on your host machine is Docker Engine 20.10 or newer. On Ubuntu or Debian, run sudo apt update && sudo apt install docker.io and verify with docker –version. On macOS, Docker Desktop handles everything. If you plan to run Gotenberg alongside other self-hosted tools, confirm your host has at least 2GB of available RAM – Chromium, which Gotenberg uses for HTML-to-PDF rendering, is memory-hungry and will OOM-kill your container on underpowered machines without proper limits set.
You do not need to clone a repository or compile anything. Gotenberg distributes through Docker Hub under the official image gotenberg/gotenberg. Before pulling, decide whether you want the latest tag or a pinned version like 8.x. Pinning a version is the safer choice for production environments because Gotenberg’s API surface does change between major versions – a future pull of latest could silently break integrations that depend on specific route naming or form field conventions.
Create a working directory for your Gotenberg configuration files: mkdir ~/gotenberg && cd ~/gotenberg. You will store your docker-compose.yml here. If your server is exposed to the internet, also decide at this stage whether you want to front Gotenberg with a reverse proxy like Nginx or Caddy, since the container itself does not handle TLS. Skipping the reverse proxy is fine for local or internal network use, but never expose port 3000 directly to the public internet without authentication in front of it.

Running the Container
Create a docker-compose.yml file in your working directory with the following content:
- image: gotenberg/gotenberg:8
- ports: “3000:3000”
- restart: unless-stopped
- command: gotenberg –chromium-disable-javascript=true –chromium-allow-list=file:///tmp/.*
The –chromium-disable-javascript flag is worth enabling by default unless your use case specifically requires JavaScript execution during rendering. Disabling it tightens the security surface and speeds up renders for documents that do not rely on client-side scripts. The –chromium-allow-list flag restricts which URLs Chromium can load during conversion – the regex shown above allows only local temp files, which prevents server-side request forgery if Gotenberg is ever exposed to untrusted input. Start the container with docker compose up -d and confirm it is running with docker ps.
Converting Documents Through the API
Gotenberg exposes a REST API, and every conversion is a POST request to a specific route. The most commonly used routes are /forms/chromium/convert/html for HTML-to-PDF, /forms/libreoffice/convert for Office documents, and /forms/pdfengines/merge for combining existing PDFs. All requests use multipart/form-data encoding, and files are submitted as form fields.
To test an HTML conversion from the command line, create a minimal HTML file called index.html and run:
- curl –request POST http://localhost:3000/forms/chromium/convert/html \
- –form files=@index.html \
- –output result.pdf
If the container is running correctly, you will receive a PDF in the current directory within a few seconds. The response is a raw binary file stream, not JSON – do not pass it through a JSON parser. For LibreOffice conversions, swap the route to /forms/libreoffice/convert and attach a .docx or .xlsx file instead. Gotenberg handles the rest internally using a bundled LibreOffice instance.
For programmatic use inside an application, the pattern is the same across languages. In Python with the requests library, open your file in binary mode, pass it to the files parameter in a POST call, and write the response content directly to disk. In Node.js, use form-data and node-fetch or Axios. The Gotenberg community maintains client libraries for several languages, but given how simple the API is, many teams write their own thin wrapper rather than adding a dependency.
One area where setups frequently break down is timeout configuration. By default, Gotenberg allows Chromium 30 seconds to render a page. Complex HTML with many external assets, large tables, or heavy CSS can exceed this limit and return a 504 error. Increase the timeout by passing –chromium-render-timeout=60 to the container command. For LibreOffice conversions of very large spreadsheets, the equivalent flag is –uno-listener-start-timeout. Neither timeout should be increased arbitrarily – set them just high enough to cover your actual workload, since runaway renders will otherwise block the worker queue.

Scaling Gotenberg horizontally is an option worth thinking through early if your application generates high conversion volume. By default, the container processes requests sequentially through a single worker. You can increase the worker count by passing –api-worker-max-concurrent-requests=4 (or higher), but each additional concurrent Chromium or LibreOffice instance consumes proportionally more RAM. A more controlled approach is to run multiple Gotenberg containers behind a load balancer and let the balancer distribute requests – this keeps individual container memory footprints predictable and makes scaling or rolling restarts easier to manage without dropping in-flight conversions.





