HTML to PDF in Docker on Linux with C# (no apt-get)
SelectPdf.Universal ships its Chromium engine, with every shared library it needs, inside a per-platform NuGet package, so a stock .NET runtime image converts HTML to PDF with nothing else installed.
Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
2Create a console app
Any .NET 8 or .NET 10 project works. To follow along, create a console app, run the install commands above inside its folder, then replace
Program.cswith the code below.dotnet new console -n PdfSample cd PdfSample
-
3Prepare the HTML
string html = """ <html> <body style="font-family: sans-serif"> <h1>Invoice #1042</h1> <p>Rendered on Linux by SelectPdf.Universal.</p> </body> </html> """;A C# raw string literal holds the page. Any HTML works here — a Razor view rendered to a string, a template with your data filled in, or a full page with CSS.
-
4Convert and save
HtmlToPdf converter = new HtmlToPdf(); PdfDocument doc = converter.ConvertHtmlString(html); doc.Save("invoice.pdf"); doc.Close();HtmlToPdf.ConvertHtmlStringreturns aPdfDocument.Savewritesinvoice.pdfandClosereleases it. The same code runs unchanged on Windows, Linux and macOS; only the native package differs. -
5Run it
dotnet run # or in the container (writes invoice.pdf to ./out): docker build -t invoice-pdf . docker run --rm -v "$PWD/out:/out" -w /out invoice-pdf
The program writes
invoice.pdfto the working folder. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
The complete Program.cs.
This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.
using SelectPdf.Universal;
string html = """
<html>
<body style="font-family: sans-serif">
<h1>Invoice #1042</h1>
<p>Rendered on Linux by SelectPdf.Universal.</p>
</body>
</html>
""";
HtmlToPdf converter = new HtmlToPdf();
PdfDocument doc = converter.ConvertHtmlString(html);
doc.Save("invoice.pdf");
doc.Close();
Open the project on GitHub → this program and the variations below, ready to dotnet run
Build and run the container.
Dockerfile
Two stages: the SDK image publishes the app, and the stock runtime image runs it. There is no apt-get line: everything the engine needs comes with the native NuGet package.
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY . . RUN dotnet publish -c Release -f net10.0 -o /out FROM mcr.microsoft.com/dotnet/runtime:10.0-noble # no apt-get: the Chromium closure ships in SelectPdf.Universal.Native.linux-x64 WORKDIR /app COPY --from=build /out ./ ENTRYPOINT ["dotnet", "/app/PdfSample.dll"]
.dockerignore
Keeps the host's build output out of the image's build context, so the publish inside the container starts clean and the context stays small.
bin/ obj/ out/
The PDF it produces.
Download invoice.pdf ↓ the file this sample produced, unedited
Common problems and fixes.
Alpine images
Alpine uses musl, and the native engines this app runs need glibc, so keep the default Debian or Ubuntu based mcr.microsoft.com/dotnet images here. An app on Alpine converts through the remote engine instead: the ghcr.io/selectpdf/engine container renders the pages, and the app references only SelectPdf.Universal. The Alpine Linux guide under Related guides shows the setup.
Chinese, Japanese, Arabic or other scripts show as boxes
The .NET base images have no fonts, and the fonts built into the library cover Latin, Greek and Cyrillic. Add the SelectPdf.Universal.Fonts package for every other script.
Running as a non-root user
Nothing extra is needed when the app is published inside the Docker build: add USER app before the entry point. The native package makes the engine readable and executable for every user.
Sizing the container
Plan for at least 1 core and 2 GB of RAM per container, and prefer 2 or more cores for consistently fast conversions under load.
Good to know.
- The Linux native package bundles the complete Chromium shared-library closure, so there is no apt-get step. Source
- A plain docker run works: no --no-sandbox, no added capabilities, no privileged mode, no --shm-size tuning. Source
- Verified on Debian 12, Ubuntu 22.04 and 24.04, and Amazon Linux 2023; the engine needs glibc 2.30 or later. Source
- Linux x64 and ARM64 are both supported; use SelectPdf.Universal.Native.linux-arm64 on ARM. Source
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.