Table of Contents

Deployment

This article walks through deploying a CobaltPdf application on each supported cloud and container platform. For the Chromium binary delivery mechanism, see Chromium Setup.

Tip

If you are building a PDF microservice that accepts JSON payloads from other applications, see HTTP Requests for the PdfRequest / PdfResponse model and complete Azure Function and ASP.NET Core endpoint examples.

Overview

CobaltPdf ships Chromium as a NuGet package — no playwright install step is needed at deploy time. You do, however, need to:

  1. Publish with the correct Runtime Identifier (RID) so only one platform binary is included.
  2. Apply the correct ExtraArgs flags for your environment (easiest via a CloudEnvironment preset).
  3. Install Chromium's system library dependencies if you are on Linux or in a container.

Docker

1. Dockerfile

Use this multi-stage Dockerfile as a starting point. It restores and builds inside the SDK image, then copies only the output to a slim runtime image with all required Chromium dependencies pre-installed.

# ── 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

# Chromium system library dependencies (Debian/Ubuntu base images)
RUN apt-get update && apt-get install -y --no-install-recommends \
    libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
    libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
    libxrandr2 libgbm1 libpango-1.0-0 libcairo2 \
    libasound2 libxshmfence1 libx11-xcb1 \
    libxfixes3 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"]

Replace MyApp with your project name. You also need a .dockerignore file in the same directory to prevent your local bin/ and obj/ folders from being copied into the container:

bin/
obj/
.vs/
*.user
Warning

The build stage uses dotnet build without the -r or --self-contained flags. Both --self-contained true and -r linux-x64 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.

2. Configure CobaltPdf

using CobaltPdf;

CobaltEngine.Configure(o =>
{
    CloudEnvironment.ConfigureForDocker(o);
    // Optionally tune pool size (default is ProcessorCount / 2)
    // o.MaxSize = 4;
});

ConfigureForDocker applies --no-sandbox, --disable-dev-shm-usage, and --disable-gpu, and sets MaxSize = ProcessorCount / 2.

Tip

If your container has /dev/shm larger than 64 MB (e.g. you set shm_size: '1gb' in docker-compose), you can omit --disable-dev-shm-usage for a small performance gain. Use a custom configure block instead of the preset in that case.

3. docker-compose.yml

services:
  myapp:
    build:
      context: .
      dockerfile: Dockerfile
    shm_size: '1gb'          # required — Chromium needs shared memory
    environment:
      - COBALTPDF_LICENSE=YOUR-LICENSE-KEY
    ports:
      - "8080:8080"
Important

The shm_size setting is required. Docker defaults to 64 MB of shared memory, which causes Chromium to crash under load. Set it to at least 256mb (recommended 1gb).


Azure

Azure App Service (Linux)

Azure App Service Linux containers expose a limited /dev/shm. Use the Azure preset:

CobaltEngine.Configure(CloudEnvironment.ConfigureForAzure);

Publish command:

dotnet build -c Release -o ./publish

Deploy:

az webapp deploy --resource-group myRG --name myApp \
    --src-path ./publish --type zip

Set the license key as an App Service application setting:

az webapp config appsettings set \
    --resource-group myRG --name myApp \
    --settings COBALTPDF_LICENSE="YOUR-LICENSE-KEY"

Read it in code:

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

Azure Functions Premium Plan

Warning

The Consumption Plan is not supported. The browser pool cannot survive cold-starts on Consumption. Use the Premium Plan (EP1 or higher) or the Dedicated (App Service) Plan.

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();

Dockerfile for Linux Azure Function:

FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0 AS base

RUN apt-get update && apt-get install -y \
    libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
    libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
    libxfixes3 libxrandr2 libgbm1 libasound2 \
    --no-install-recommends && rm -rf /var/lib/apt/lists/*

WORKDIR /home/site/wwwroot
COPY ./publish .

Azure Container Apps

Container Apps runs standard Docker containers on Linux. Use the Docker preset and follow the Docker steps above. Scale rules should be based on HTTP concurrency, not instance count, so the pool is shared within each replica.


AWS

AWS ECS / Fargate

Use the ECS preset, which omits --single-process for better stability under sustained load:

CobaltEngine.Configure(CloudEnvironment.ConfigureForAwsEcs);

Task definition recommendations:

Resource Minimum Recommended
vCPU 0.5 1.0
Memory 1 GB 2 GB
/dev/shm Set linuxParameters.sharedMemorySize: 256

Dockerfile — same as the standard Docker steps above.

Publish:

dotnet build -c Release -o ./publish
docker build -t myapp:latest .
docker tag myapp:latest <account>.dkr.ecr.<region>.amazonaws.com/myapp:latest
docker push <account>.dkr.ecr.<region>.amazonaws.com/myapp:latest

Set the license key as an ECS environment variable or via AWS Secrets Manager.

AWS Lambda (Custom Container Runtime)

Warning

Lambda imposes a 512 MB /tmp limit and the default memory ceiling is 10 GB. Browser pool persistence across invocations is not guaranteed — each cold start may launch a new Chromium instance. For high-volume PDF generation, prefer ECS or App Runner.

Use the low-memory preset which sets MaxSize = 1 and enables --single-process:

CobaltEngine.Configure(CloudEnvironment.ConfigureForLowMemory);

Lambda requires a custom container image (not the managed runtime):

FROM public.ecr.aws/lambda/dotnet:8 AS base

RUN yum install -y \
    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 Lambda configuration:

Setting Value
Memory 2048 MB
Timeout 60 seconds
Architecture x86_64

Linux VM / Bare Metal

Use the Linux preset:

CobaltEngine.Configure(CloudEnvironment.ConfigureForLinux);

Install Chromium's system libraries via your package manager:

# Debian / Ubuntu
sudo apt-get install -y \
    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 \
    nss atk at-spi2-atk cups-libs libdrm \
    libxkbcommon libXcomposite libXdamage \
    libXfixes libXrandr mesa-libgbm alsa-lib

Publish:

dotnet build -c Release -o ./publish

CI/CD Tips

GitHub Actions — Linux runner

- name: Install Chromium dependencies
  run: |
    sudo apt-get update
    sudo apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 \
      libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
      libxfixes3 libxrandr2 libgbm1 libasound2

- name: Publish
  run: dotnet build -c Release -o ./publish

Environment variable for license key

Store your license key in a CI secret and pass it through as an environment variable:

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
Azure App Service (Linux) ConfigureForAzure Set license via App Settings
Azure Functions Premium ConfigureForAzure Do not use Consumption Plan
Azure Container Apps ConfigureForDocker Standard Docker container
AWS ECS / Fargate ConfigureForAwsEcs Set sharedMemorySize ≥ 256 MB
AWS Lambda ConfigureForLowMemory Custom container; pool may restart on cold start
GitHub Actions (Linux) ConfigureForLinux Install system deps in workflow