Docker and Linux
Run digital signature from the .NET 8 SDK on Linux and in containers. The SecureExchanges.Signature.NetCore module certifies PDFs with headless Chromium, driven by PuppeteerSharp; on Linux, Chromium needs system libraries that the image must provide.
This setup applies to any Linux container host: Azure Functions, Azure Container Apps, Azure App Service (Linux), Kubernetes.
Prerequisites
- A .NET 8 or later project that references
SecureExchanges.Signature.NetCore. - Docker, or an image build service such as
az acr build. - The Chrome for Testing version expected by your SDK version (see below).
Where the SDK looks for Chromium
| Order | Location |
|---|---|
| 1 | A Chromium placed next to the application: chromium/chrome-linux64/chrome on Linux, chromium/chrome-win64/chrome.exe on Windows |
| 2 | The path given by the SE_PUPPETEER_EXECUTABLE_PATH environment variable |
| 3 | The PuppeteerSharp local cache, which downloads Chromium on first use, unless SE_DISABLE_AUTO_DOWNLOAD is 1 or true |
In production, put Chromium in the image and block the download: startup no longer depends on the network, and you know exactly which binary runs.
Dockerfile
Two-stage build: the first stage compiles the project, the second installs Chromium's dependencies, adds Chrome for Testing next to the application and copies the output.
Note
This Dockerfile targets Azure Functions (base image and Functions-specific variables). For another host, replace the runtime image with mcr.microsoft.com/dotnet/aspnet:8.0 or equivalent, and adjust the entry point. A complete project is available on GitHub: AzureFunctionsSignature.netCore.
# Azure Functions .NET 8 (isolated worker) with headless Chromium.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["AzureFunctionsSignature.netCore.csproj", "./"]
RUN dotnet restore "AzureFunctionsSignature.netCore.csproj"
COPY . .
RUN dotnet publish "AzureFunctionsSignature.netCore.csproj" \
-c Release \
-o /app/publish \
--no-restore \
/p:UseAppHost=false
FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0 AS runtime
# Chromium dependencies for Debian bookworm.
# If a future Chrome version fails with "error while loading shared libraries: libXXX.so",
# add the library here and rebuild the image.
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 \
libcairo2 libcups2 libdbus-1-3 libdrm2 libexpat1 libfontconfig1 libgbm1 \
libglib2.0-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 \
libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
libxfixes3 libxi6 libxkbcommon0 libxrandr2 libxrender1 libxshmfence1 libxss1 libxtst6 \
wget unzip \
&& rm -rf /var/lib/apt/lists/*
# Chrome for Testing: version aligned with PuppeteerSharp's (see the table below).
ARG CHROME_VERSION=146.0.7680.153
# SHA-256 hash of the file, required: the image does not build if the downloaded file
# does not match.
ARG CHROME_SHA256
RUN test -n "$CHROME_SHA256" \
&& wget -q "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" \
-O /tmp/chrome.zip \
&& echo "${CHROME_SHA256} /tmp/chrome.zip" | sha256sum -c - \
&& mkdir -p /home/site/wwwroot/chromium \
&& unzip -q /tmp/chrome.zip -d /home/site/wwwroot/chromium \
&& chmod +x /home/site/wwwroot/chromium/chrome-linux64/chrome \
&& rm /tmp/chrome.zip
# Chromium is in the image: no download at runtime.
# One worker per instance: a single Chromium process, less memory.
ENV SE_DISABLE_AUTO_DOWNLOAD=1 \
FUNCTIONS_WORKER_PROCESS_COUNT=1
COPY --from=build /app/publish /home/site/wwwroot
Build the image by passing the file's hash:
docker build --build-arg CHROME_SHA256=<SHA-256 hash of chrome-linux64.zip> -t pki-signature .
To get the hash: download the file once from the official Chrome for Testing address, compute it (sha256sum chrome-linux64.zip, or Get-FileHash on Windows), then pin it in your pipeline. It changes whenever CHROME_VERSION changes.
Chrome for Testing version
PuppeteerSharp expects a specific Chrome for Testing version; the image must install that one.
SecureExchanges.Signature.NetCore version |
Chrome for Testing |
|---|---|
| 26.4.23.1 and later | 146.0.7680.153 |
For an SDK version newer than this table:
- Find the version PuppeteerSharp expects: on first launch without Chromium, it downloads it to
/tmp/Chrome/Linux-<version>/. - Pick the same version on the Chrome for Testing dashboard.
- Update
CHROME_VERSIONandCHROME_SHA256.
A version mismatch can prevent Chromium from starting or subtly change the rendering.
Environment variables
| Variable | Purpose |
|---|---|
SE_PUPPETEER_EXECUTABLE_PATH |
Absolute path to a Chromium binary, used when no Chromium is placed next to the application. |
SE_DISABLE_AUTO_DOWNLOAD |
1 or true: prevents PuppeteerSharp from downloading Chromium at runtime. Recommended whenever the image contains Chromium. |
FUNCTIONS_WORKER_PROCESS_COUNT |
Specific to Azure Functions: see Azure Functions. |
Warning
Security.
- Integrity: do not remove the SHA-256 check on the Chrome download; it guarantees the image contains the expected binary.
- Updates: a pinned Chrome version no longer receives fixes. Update it with the SDK, and rebuild the image regularly for system fixes.
- Base images: for reproducible builds, pin images by digest (
image@sha256:...) rather than by tag. - Credentials: never put them in the image or the Dockerfile; read them at runtime from your secret manager.
Next
- Azure Functions: registry, managed identity, Function App image
- Digital signature: using the SDK