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:
- Publish with the correct Runtime Identifier (RID) so only one platform binary is included.
- Apply the correct
ExtraArgsflags for your environment (easiest via aCloudEnvironmentpreset). - 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 |