Stock Linux plans now work (v1.6.2+). CobaltPDF 1.6.2 bundles Chromium's system libraries and loads them automatically, so the Chromium edition deploys to a stock Linux Functions plan with a normal code deploy — no custom container, no apt. Windows plans also deploy as plain code. (On versions before 1.6.2, Linux required a custom container.)

1 Choose a plan

Consumption Plan is not supported. It aggressively recycles instances and can't keep the browser pool warm, so renders are slow and unreliable.

Windows plan — any Dedicated (B1+) or Premium plan; Chromium's bundled Windows binary runs natively with a normal code deploy.

Linux plan (v1.6.2+) — any stock Dedicated (B1+) or Premium plan with Always On, deployed as plain code (the package ships Chromium's libraries). We recommend B2 (3.5 GB) as the minimum — Chromium's memory peaks mean a 1.75 GB B1 can OOM on heavy or image-rich pages. A custom container (step 5) remains an option if you'd rather bake everything into one image.

2 Create the project

Terminal
func init CobaltPdfFunc --dotnet-isolated -n net8.0
cd CobaltPdfFunc
dotnet add package CobaltPDF

3 Configure the engine

On Linux, apply the Azure preset. On Windows, no preset is needed.

Linux plan

Program.cs
using CobaltPdf;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        services.AddCobaltPdf(o =>
        {
            CloudEnvironment.ConfigureForAzure(o);
            o.MaxSize = 1;   // 1 on EP1 (3.5 GB); 2 on EP2+ (7 GB)
        });
    })
    .Build();

CobaltEngine.SetLicense(
    Environment.GetEnvironmentVariable("COBALTPDF_LICENSE")!);
await CobaltEngine.PreWarmAsync();

host.Run();

Windows plan

Program.cs
using CobaltPdf;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        // No cloud preset needed on Windows
        services.AddCobaltPdf(o =>
        {
            o.MaxSize = 2;
        });
    })
    .Build();

CobaltEngine.SetLicense(
    Environment.GetEnvironmentVariable("COBALTPDF_LICENSE")!);

host.Run();

4 Create the HTTP function

Accepts HTML in the POST body and returns a PDF. The only line that differs between engines is the using.

RenderPdf.cs
using System.Net;
using CobaltPdf;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

public class RenderPdf
{
    [Function("RenderPdf")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
    {
        var html = await new StreamReader(req.Body).ReadToEndAsync();

        var pdf = await new CobaltEngine()
            .WithPaperFormat("A4")
            .RenderHtmlAsPdfAsync(html);

        var response = req.CreateResponse(HttpStatusCode.OK);
        response.Headers.Add("Content-Type", "application/pdf");
        await response.Body.WriteAsync(pdf.BinaryData);
        return response;
    }
}
Building a PDF microservice? Use the shared CobaltPdf.Requests wire model — clients POST a PdfRequest as JSON and never need to know which engine the service runs.

5 Deploy

Code deploy — Windows, and Linux on v1.6.2+

Do not use -r or --self-contained — both flatten the runtimes/ folder and break Chromium path resolution.
Terminal
dotnet publish -c Release -o ./publish
func azure functionapp publish <YOUR_APP_NAME>
Deploying to a Linux plan? Publish from Linux or CI — not from a Windows machine. A Windows toolchain (and func publish from Windows) zips Chromium's node/chrome binaries without the Unix execute bit, and the read-only package mount can't restore it — so the function fails at render time with node: Permission denied. Deploy from a Linux build agent / GitHub Actions / WSL (which preserve the execute bit), or use the custom-container option below. (Windows-plan deploys are unaffected.)

Linux — custom container (optional)

Not required on v1.6.2+ — the code deploy above works on a stock Linux plan. Use a container only if you prefer to bake everything into one image, or on versions before 1.6.2. Build an image on the Functions base with Chromium's system libraries, push it to a registry, and create the function app from the image:

Dockerfile
FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0
ENV AzureWebJobsScriptRoot=/home/site/wwwroot

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
Azure CLI
dotnet publish -c Release -o ./publish

az acr create -n <ACR> -g <RG> --sku Basic --admin-enabled true
az acr login -n <ACR>
docker build -t <ACR>.azurecr.io/pdf-func:1 .
docker push <ACR>.azurecr.io/pdf-func:1

az functionapp create -n <APP> -g <RG> \
  --plan <PREMIUM_PLAN> --storage-account <STORAGE> \
  --functions-version 4 --os-type Linux \
  --image <ACR>.azurecr.io/pdf-func:1 \
  --registry-server https://<ACR>.azurecr.io \
  --registry-username <ACR> \
  --registry-password "$(az acr credential show -n <ACR> --query 'passwords[0].value' -o tsv)"

6 Test

Terminal
curl -X POST \
  "https://<APP>.azurewebsites.net/api/RenderPdf?code=<KEY>" \
  -d "<h1>Hello from Azure!</h1>" \
  -o output.pdf

Troubleshooting

"error while loading shared libraries: libglib-2.0.so.0"

You're on a version before 1.6.2. Upgrade to CobaltPDF 1.6.2 or later — it ships Chromium's system libraries and loads them automatically, so a stock Linux plan works with a plain code deploy. (On older versions, deploy as a custom container in step 5.)

Chromium path not found

Your publish or CI/CD pipeline used -r linux-x64 or --self-contained. Remove them: dotnet publish -c Release -o ./publish.

Out of memory / intermittent render failures

The plan is too small. Chromium needs ≥ 3.5 GB per instance: MaxSize = 1 on EP1, MaxSize = 2 on EP2+. Monitor Metrics > Memory working set.

Crashes on Consumption

Consumption is not supported. Scale up to Premium (EP1+) or a Dedicated plan ≥ 3.5 GB.