1 Choose a plan
Consumption Plan is not supported.
It aggressively recycles instances and has limited memory. Chromium cannot start reliably.
Use a Premium (EP1+) or Dedicated (B2+) plan.
Both Windows and Linux are supported — choose whichever fits your environment.
- Windows plan — No special Chromium configuration needed. CobaltPDF uses the bundled Windows binary automatically.
- Linux plan — Requires
ConfigureForAzure to set Chromium sandbox and GPU flags.
2 Create the project
func init CobaltPdfFunc --dotnet-isolated -n net8.0
cd CobaltPdfFunc
dotnet add package CobaltPDF
3 Configure the engine
The configuration differs by OS. On Windows, no cloud preset is needed.
On Linux, call ConfigureForAzure.
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 = 2;
});
})
.Build();
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();
host.Run();
4 Create the HTTP function
This function accepts HTML in the POST body and returns a PDF file. The
CobaltEngine is shared across all invocations via DI.
using System.Net;
using CobaltPdf;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
namespace CobaltPdfFunc;
public class RenderPdf
{
private readonly CobaltEngine _engine;
public RenderPdf(CobaltEngine engine)
=> _engine = engine;
[Function("RenderPdf")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function,
"post")] HttpRequestData req)
{
var html = await new StreamReader(req.Body)
.ReadToEndAsync();
var pdf = await _engine
.WithPaperFormat("A4")
.PrintBackground()
.RenderHtmlAsPdfAsync(html);
var response = req.CreateResponse(
HttpStatusCode.OK);
response.Headers.Add(
"Content-Type", "application/pdf");
await response.Body.WriteAsync(pdf.BinaryData);
return response;
}
}
5 Deploy
Do not use -r linux-x64 or --self-contained.
Both flatten the runtimes/ folder and break Chromium path resolution.
Azure Functions Core Tools
dotnet publish -c Release -o ./publish
cd publish
func azure functionapp publish <YOUR_APP_NAME>
Visual Studio
Right-click the project → Publish → select your Function App.
Visual Studio publishes without -r by default.
GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- run: dotnet publish -c Release -o ./publish
- uses: Azure/functions-action@v1
with:
app-name: <YOUR_APP_NAME>
package: ./publish
publish-profile: ${{ secrets.PUBLISH_PROFILE }}
6 Test
curl -X POST \
"https://<APP>.azurewebsites.net/api/RenderPdf?code=<KEY>" \
-d "<h1>Hello from Azure!</h1>" \
-o output.pdf
Troubleshooting
Chromium path not found
Your publish or CI/CD pipeline is using -r linux-x64.
Remove it. Use dotnet publish -c Release -o ./publish without -r.
Timeout on first invocation
Cold start takes 1-3 seconds while Chromium launches. Use a Dedicated plan
with Always On enabled for latency-sensitive workloads.
Out of memory
Set MaxSize = 1 on EP1 (3.5 GB) or MaxSize = 2 on EP2+ (7 GB).
Monitor via Metrics > Memory working set in the Azure Portal.
Chromium crashes on Consumption
Switch to a Premium (EP1+) or Dedicated (B2+) plan under
App Service plan > Scale up.