Click or drag to resize
Pdf Library for .NET

Deployment with Docker

Docker packages an application together with everything it needs to run into a portable container image, which then runs identically on a developer machine, an on-premise server or any cloud container service (Azure Container Apps / AKS, AWS ECS / EKS, Google Cloud Run, and so on).

SelectPdf is container-ready: reference the NuGet packages, publish, run — no extra installation steps inside the image. Both Linux containers and Windows containers are supported.

Linux containers

Reference the managed SelectPdf.Universal package together with the SelectPdf.Universal.Native.linux-x64 (or linux-arm64) package. dotnet publish places the native engines in the output automatically, and nothing else needs to be installed in the image:

  • No system packages / no apt-get — the native Linux package bundles the complete Chromium shared-library closure (including the runtime-loaded NSS / SQLite modules required for HTTPS), and SelectPdf wires it up automatically when it launches the engine. The HTML-to-PDF and HTML-to-image converters run on the bare mcr.microsoft.com/dotnet/aspnet / runtime images.

  • No display server — the Chromium engine renders headless / off-screen; no X server or Xvfb.

  • Fonts from a package, not from the image — the base images have no fonts, and the fonts built into the library cover Latin, Greek and Cyrillic only. Reference the SelectPdf.Universal.Fonts package (Noto fonts for every script, copied into the publish output) to render every language - see Fonts and Languages.

  • No special docker run flags — no --no-sandbox, no extra capabilities, no privileged mode. A plain docker run works.

  • No manual chmod — the native package makes the engine executable when the application is published on Linux, and the library restores the permission before launching the engine if it was lost (see the note about non-root containers below).

  • No GPU and no --shm-size tuning required — the engine renders in software, and Chromium does not keep its shared memory in /dev/shm, so Docker's default 64 MB is enough. The engine stays loaded inside the application between conversions (ChromiumEngineHostMode, the default) in a plain docker run as well.

  • The PDF-to-text and PDF-to-image converters use a statically-linked native tool with no dependencies at all, and the rest of the library is fully managed.

Base images: any glibc-based .NET image works. Verified end-to-end (a real HTTPS page converted, with no packages installed): the default Debian 12 based aspnet:8.0 / runtime:8.0 tags, Ubuntu 22.04 (8.0-jammy), Ubuntu 24.04 (10.0-noble), and — via a self-contained publish — the RPM family (amazonlinux:2023, zero dnf packages). The engines require glibc 2.30 or newer — satisfied by every mainstream distribution since about 2020 (Ubuntu 20.04+, Debian 11+, RHEL 9+).

Resources: Chromium-based rendering benefits from CPU and memory — plan for at least 1 core and 2 GB of RAM per container, and prefer 2+ cores for consistently fast conversions under load.

A minimal multi-stage Dockerfile for an ASP.NET application using SelectPdf (any supported .NET version works the same way):

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish MyApp.csproj -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]

Build and run:

docker build -t myapp .
docker run --rm -p 8080:8080 myapp
Important note  Important

Use a glibc-based Linux image (the default Debian / Ubuntu based mcr.microsoft.com/dotnet tags). Alpine (musl) images are not supported by the native engines.

Alternative: publish on the host, copy into the image

If restoring NuGet packages inside the Docker build is inconvenient (private feeds, offline build agents), publish on the host first — dotnet publish -c Release -o publish — and use a single-stage Dockerfile that just copies the publish output. The native engines are already in the publish folder, so the result is identical:

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY publish/ .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]

Running the container as a non-root user

A non-root user needs nothing extra. When the application is published on Linux - inside the Docker build, as above - the native package makes the engine files readable and executable for every user, so the image can switch to the built-in app user before the entry point:

USER app
ENTRYPOINT ["dotnet", "MyApp.dll"]

An application published on Windows and copied into the image gets the same permissions from Docker, which marks files copied from a Windows build context as executable for every user. Verified: conversions run identically under the app user, on Docker and on platforms that pick the user themselves (AWS Lambda, Google Cloud Run functions).

Linux ARM64

For ARM64 containers, reference SelectPdf.Universal.Native.linux-arm64 instead of linux-x64 — everything else stays the same (the mcr.microsoft.com/dotnet base tags are multi-architecture, so the same image reference selects the matching architecture automatically). On an ARM64 host, build and run as usual; from an x64 host you can cross-build and run under emulation:

docker build --platform linux/arm64 -t myapp-arm64 .
docker run --rm --platform linux/arm64 -p 8080:8080 myapp-arm64
Note  Note

This is also the package to use when building on an Apple Silicon Mac. Docker Desktop for Mac runs Linux containers, so a container image needs SelectPdf.Universal.Native.linux-arm64 - not osx-arm64, which is for applications running natively on macOS.

Windows containers

Reference the managed package together with SelectPdf.Universal.Native.win-x64 and base the runtime image on Windows Server Core — for example mcr.microsoft.com/dotnet/aspnet:8.0-windowsservercore-ltsc2022. Server Core provides the GDI components the Chromium engine uses; the smaller Nano Server images do not and are not supported. No Visual C++ Redistributable is required — the native engines are static builds.

One container-specific step is required in the image (illustrated by the shipped Docker samples and verified end-to-end): install the core Windows font families. Server Core images ship almost no fonts, and the Chromium engine cannot start on a fontless image — DirectWrite's font fallback fails fatally when none of the installed families are ones it recognizes (a set of only third-party fonts is not sufficient; the standard Windows families — Segoe UI, Arial, Times New Roman, Verdana, Tahoma — are). Copy them from Microsoft's full Windows Server image in a build stage and register them:

FROM mcr.microsoft.com/windows/server:ltsc2022 AS fonts
# ... in the runtime stage:
COPY --from=fonts ["C:/Windows/Fonts/segoeui.ttf", "C:/Windows/Fonts/segoeuib.ttf", \
                   "C:/Windows/Fonts/arial.ttf", "C:/Windows/Fonts/arialbd.ttf", \
                   "C:/Windows/Fonts/"]
RUN powershell -Command "Get-ChildItem C:\Windows\Fonts -Filter *.ttf | \
    ForEach-Object { New-ItemProperty \
    -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts' \
    -Name ($_.BaseName + ' (TrueType)') -Value $_.Name \
    -PropertyType String -Force | Out-Null }"

Add the italic variants and any other families your pages use the same way. Alternatively, base the runtime on the full Windows Server image (mcr.microsoft.com/windows/server:ltsc2022, which ships the complete font set) and install the ASP.NET Core runtime in the Dockerfile — no font step is needed there, at the cost of a larger image.

Nothing else is required: the engine detects that it is running in a Windows container and automatically disables the DirectX shader-compiler path that would otherwise crash on display-less hosts — rendering proceeds in software, which is exactly right for PDF and image output. (The environment variable SELECTPDF_BLOCK_D3DCOMPILER — 1/0 — can force this behavior on or off if ever needed.)

Windows container images can only be built and run on a Windows host with Docker switched to Windows-container mode. When Windows itself is not a requirement, prefer the Linux containers above — they are smaller and need no font step at all.

Run-once (batch) containers

For batch conversion jobs, package a console application instead of a web application and mount a host folder to collect the produced files:

docker run --rm -v "$PWD/out:/out" myconverter --url https://selectpdf.com -o /out/site.pdf

The container starts, performs the conversion, writes the PDF to the mounted folder and exits.

Ready-to-run Docker samples

The official SelectPdf samples include complete Docker samples — multi-stage Dockerfiles (with compose.yaml files and per-sample instructions) that containerize the sample web application and a command-line converter, for both Linux and Windows containers. They are a working starting point for your own images.

The images you build are standard OCI images — push them to any container registry (Azure Container Registry, Amazon ECR, Docker Hub, GitHub Container Registry) and deploy them from your orchestrator as usual.

See Also