Deployment
This article walks through deploying on each supported cloud and container platform — for both engines. Every platform section below has two tabs:
- CobaltPdf (Chromium) — ships Chromium in the NuGet package. Chromium depends on system libraries (
libglib,libnss3, the GTK/X11 stack), so Linux deployments need those libraries present — which on some platforms means deploying as a custom container. - CobaltPDF.WebKit — downloads a fully self-contained WebKit bundle at first start (everything except
glibcis included). No system libraries to install, so it runs on stock platforms — including code-only Azure Functions and App Service Linux plans — with no container required. See the WebKit edition overview and the dedicated WebKit docs.
Pick your engine tab once — every section on this page follows it.
Tip
If you are building a PDF microservice that accepts JSON payloads from other applications,
see HTTP Requests for the PdfRequest / PdfResponse model. The same
wire model works against either engine, so clients never need to know which one the service runs.
Choosing an engine for deployment
| CobaltPdf (Chromium) | CobaltPDF.WebKit | |
|---|---|---|
| Linux system libraries | Required (apt/yum packages) | None — bundle is self-contained |
| Stock (code-only) Azure Functions Linux | ❌ Not possible — must use a custom container | ✅ Works — zip deploy |
| Stock Azure App Service Linux | ⚠ Depends on image; container recommended | ✅ Works — zip deploy |
| Docker | ✅ Custom image with system packages | ✅ Any glibc ≥ 2.35 base image, no packages |
| Deployment artifact size | ~460 MB (Chromium ships in the package) | ~3 MB (bundle downloads at first start, ~262 MB, cached) |
| Typical memory per instance | 1.5–2.5 GB peak | 0.3–1.3 GB peak |
| Windows servers / containers | ✅ Native | Dev-only (WSL2 / Docker); production targets Linux |
Azure Functions (Linux)
Warning
A code-only (zip) deployment to a Linux Functions plan does not work for Chromium.
Azure's stock Functions image does not contain Chromium's system libraries and they cannot
be installed on a stock plan. The browser fails to launch with:
error while loading shared libraries: libglib-2.0.so.0: cannot open shared object file.
Deploy as a custom container instead (steps below). The Consumption plan is not
supported either way — use Premium (EP1 or higher) or a Dedicated plan with at least
3.5 GB memory (1.75 GB B1 instances are below Chromium's floor and OOM under load).
1. Configure the function app in Program.cs:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddCobaltPdf(o =>
{
CloudEnvironment.ConfigureForAzure(o);
});
})
.Build();
CobaltEngine.SetLicense(Environment.GetEnvironmentVariable("COBALTPDF_LICENSE")!);
await CobaltEngine.PreWarmAsync();
await host.RunAsync();
2. Publish — without -r or --self-contained (both flatten the runtimes/ folder and break Chromium.Path resolution):
dotnet publish -c Release -o ./publish
3. Dockerfile — the Functions base image plus Chromium's system libraries:
FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
RUN apt-get update && apt-get install -y --no-install-recommends \
libglib2.0-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
libcups2 libdrm2 libxkbcommon0 libatspi2.0-0 libx11-6 \
libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 \
libgbm1 libpango-1.0-0 libcairo2 libasound2 \
fonts-liberation fonts-dejavu-core \
&& rm -rf /var/lib/apt/lists/*
COPY ./publish /home/site/wwwroot
4. Build, push to a registry, and create the container-based function app:
az acr create -n myacr -g myRG --sku Basic --admin-enabled true
az acr login -n myacr
docker build -t myacr.azurecr.io/pdf-func:1 .
docker push myacr.azurecr.io/pdf-func:1
az functionapp create -n my-pdf-func -g myRG \
--plan my-premium-plan --storage-account mystorage \
--functions-version 4 --os-type Linux \
--image myacr.azurecr.io/pdf-func:1 \
--registry-server https://myacr.azurecr.io \
--registry-username myacr --registry-password "$(az acr credential show -n myacr --query 'passwords[0].value' -o tsv)"
az functionapp config appsettings set -n my-pdf-func -g myRG \
--settings COBALTPDF_LICENSE="YOUR-LICENSE-KEY"
Azure App Service (Linux)
App Service Linux code-only deployments run on Azure's stock runtime image. Like the Functions image, it may not include all of Chromium's system libraries.
Warning
If your app fails its first render with error while loading shared libraries: …, the
stock image is missing Chromium's dependencies and you should deploy as a custom
container instead — use the Docker steps below with az webapp create --deployment-container-image-name.
Use the Azure preset either way:
CobaltEngine.Configure(CloudEnvironment.ConfigureForAzure);
Code-only attempt (works only if the stock image carries the libraries):
dotnet publish -c Release -o ./publish
az webapp deploy --resource-group myRG --name myApp --src-path ./publish --type zip
az webapp config appsettings set -g myRG -n myApp --settings COBALTPDF_LICENSE="YOUR-LICENSE-KEY"
Docker
Use a multi-stage Dockerfile; the runtime stage installs Chromium's system libraries.
# ── Build stage ─────────────────────────────────────────────────
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY MyApp.csproj ./
RUN dotnet restore MyApp.csproj
COPY . .
RUN dotnet build MyApp.csproj -c Release --no-restore -o /app/publish
# ── Runtime stage ──────────────────────────────────────────────
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
libglib2.0-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
libcups2 libdrm2 libxkbcommon0 libatspi2.0-0 \
libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 \
libpango-1.0-0 libcairo2 libasound2 libxshmfence1 libx11-xcb1 \
libxss1 libxtst6 \
fonts-liberation fonts-noto-color-emoji \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Warning
The build stage uses dotnet build without the -r or --self-contained flags.
Both flatten the runtimes/{rid}/native/ folder into the output root, which breaks
Chromium.Path resolution. If Chromium.Path returns null at runtime, this is the
most likely cause.
Configure with the Docker preset, and give Chromium shared memory in compose:
CobaltEngine.Configure(o =>
{
CloudEnvironment.ConfigureForDocker(o);
});
services:
myapp:
build: { context: ., dockerfile: Dockerfile }
shm_size: '1gb' # required — Chromium needs shared memory
environment:
- COBALTPDF_LICENSE=YOUR-LICENSE-KEY
ports:
- "8080:8080"
Important
shm_size is required. Docker defaults to 64 MB of shared memory, which causes
Chromium to crash under load. Set at least 256mb (recommended 1gb).
Azure Container Apps
Container Apps runs standard Docker containers — use the Docker image above with the Docker preset. Give each replica at least 2 Gi memory (4 Gi for production). Scale on HTTP concurrency, not instance count, so the browser pool is shared within each replica.
AWS ECS / Fargate
Use the ECS preset, which omits --single-process for better stability under sustained load:
CobaltEngine.Configure(CloudEnvironment.ConfigureForAwsEcs);
| Resource | Minimum | Recommended |
|---|---|---|
| vCPU | 0.5 | 1.0 |
| Memory | 1 GB | 2 GB |
/dev/shm |
— | linuxParameters.sharedMemorySize: 256 |
Build with the Docker steps above, push to ECR, set the license key via task environment variables or AWS Secrets Manager.
AWS Lambda
Warning
Lambda imposes a 512 MB /tmp default and browser-pool persistence across invocations
is not guaranteed. For high-volume PDF generation prefer ECS or App Runner.
Use the low-memory preset (MaxSize = 1, --single-process) and a custom container image:
CobaltEngine.Configure(CloudEnvironment.ConfigureForLowMemory);
FROM public.ecr.aws/lambda/dotnet:8 AS base
RUN yum install -y \
glib2 nss atk at-spi2-atk cups-libs \
libdrm libxkbcommon libXcomposite libXdamage \
libXfixes libXrandr mesa-libgbm alsa-lib \
&& yum clean all
WORKDIR /var/task
COPY ./publish .
CMD ["MyApp::MyApp.LambdaEntryPoint::FunctionHandlerAsync"]
Recommended: 2048 MB memory, 60 s timeout, x86_64.
Linux VM / Bare Metal
Use the Linux preset and install Chromium's system libraries:
CobaltEngine.Configure(CloudEnvironment.ConfigureForLinux);
# Debian / Ubuntu
sudo apt-get install -y \
libglib2.0-0 libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
libxfixes3 libxrandr2 libgbm1 libasound2
# RHEL / Amazon Linux 2023
sudo yum install -y \
glib2 nss atk at-spi2-atk cups-libs libdrm \
libxkbcommon libXcomposite libXdamage \
libXfixes libXrandr mesa-libgbm alsa-lib
Windows development machines
Works natively — no preset, no extra setup. The bundled Windows Chromium binary is used automatically, on dev machines and Windows servers/containers alike.
CI/CD Tips
- name: Install Chromium dependencies
run: |
sudo apt-get update
sudo apt-get install -y libglib2.0-0 libnss3 libatk1.0-0 libatk-bridge2.0-0 \
libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
libxfixes3 libxrandr2 libgbm1 libasound2
- name: Publish
run: dotnet publish -c Release -o ./publish
Store the license key in a CI secret for either engine:
env:
COBALTPDF_LICENSE: ${{ secrets.COBALTPDF_LICENSE }}
CobaltEngine.SetLicense(Environment.GetEnvironmentVariable("COBALTPDF_LICENSE")!);
Platform Summary
| Platform | Preset | Notes |
|---|---|---|
| Windows (local / IIS) | (none needed) | Chromium sandbox works natively |
| Linux VM / bare metal | ConfigureForLinux |
Install system libs |
| Docker | ConfigureForDocker |
Include apt deps in Dockerfile; shm_size ≥ 256mb |
| Azure App Service (Linux) | ConfigureForAzure |
Custom container recommended |
| Azure Functions (Linux) | ConfigureForAzure |
Custom container required; EP1+ / ≥ 3.5 GB |
| Azure Container Apps | ConfigureForDocker |
≥ 2 Gi memory per replica |
| AWS ECS / Fargate | ConfigureForAwsEcs |
sharedMemorySize ≥ 256 MB |
| AWS Lambda | ConfigureForLowMemory |
Custom container; pool may restart per cold start |