Table of Contents

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 glibc is 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