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
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
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
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.
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.
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:
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
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
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.