Deployment
This article walks through deploying on each supported cloud and container platform — for both engines. Every platform section below has two tabs:
- CobaltPdf (Chromium) — the Chromium edition ships Chromium in the NuGet package. Chromium depends on system libraries (
libglib,libnss3, the GTK/X11 stack), so Linux deployments need those libraries present — which on some platforms means deploying as a custom container. - CobaltPDF.WebKit — downloads a fully self-contained WebKit bundle at first start (everything except
glibcis included). No system libraries to install, so it runs on stock platforms — including code-only Azure Functions and App Service Linux plans — with no container required. This is the engine documented on this site.
Pick your engine tab once — every section on this page follows it.
Tip
If you are building a PDF microservice that accepts JSON payloads from other applications,
see the CobaltPDF.Requests docs for the PdfRequest / PdfResponse model. The same
wire model works against either engine, so clients never need to know which one the service runs.
Choosing an engine for deployment
| CobaltPdf (Chromium) | CobaltPDF.WebKit | |
|---|---|---|
| Linux system libraries | Required (apt/yum packages) | None — bundle is self-contained |
| Stock (code-only) Azure Functions Linux | ❌ Not possible — must use a custom container | ✅ Works — zip deploy |
| Stock Azure App Service Linux | ⚠ Depends on image; container recommended | ✅ Works — zip deploy |
| Docker | ✅ Custom image with system packages | ✅ Any glibc ≥ 2.35 base image, no packages |
| Deployment artifact size | ~460 MB (Chromium ships in the package) | ~3 MB (bundle downloads at first start, ~262 MB, cached) |
| Typical memory per instance | 1.5–2.5 GB peak | 0.3–1.3 GB peak |
| Windows servers / containers | ✅ Native | Dev-only (WSL2 / Docker); production targets Linux |
Azure Functions (Linux)
Note
A code-only (zip) deployment to a stock Linux Functions plan works. No container, no registry, no system packages. The library downloads its self-contained render bundle (~262 MB) on each instance's first start and caches it. Validated end-to-end on a stock B1 Linux plan; for interactive latency use EP1 or higher — B1's single burstable core suits queue/batch workloads. The classic Consumption plan is not supported (its writable temp disk is smaller than the extracted bundle).
1. Configure the function app in Program.cs:
using CobaltPdf.WebKit;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();
CobaltEngine.SetLicense(Environment.GetEnvironmentVariable("COBALTPDF_LICENSE")!);
CobaltEngine.Configure(o =>
{
o.MinSize = 1;
o.MaxSize = 1; // 1 per ~1.5 GB of plan memory
o.MaxMemoryMb = 2048; // clean error instead of OOM-kill
o.BrowserLeaseTimeout = TimeSpan.FromSeconds(180); // queued requests outlive a slow render
});
// Downloads + extracts the bundle and warms a render worker off the request path.
_ = Task.Run(() => CobaltEngine.PreWarmAsync());
host.Run();
2. App settings — one is critical:
az functionapp config appsettings set -n my-pdf-func -g myRG --settings \
COBALT_BUNDLE_CACHE_DIR=/tmp/cobaltbundle \
COBALTPDF_LICENSE="YOUR-LICENSE-KEY" \
SCM_DO_BUILD_DURING_DEPLOYMENT=false ENABLE_ORYX_BUILD=false
Important
COBALT_BUNDLE_CACHE_DIR=/tmp/cobaltbundle keeps the bundle on the instance's local
disk. Without it, the cache lands on /home — an Azure Files network share where
extracting thousands of files takes 10–100× longer and the first render times out.
3. Publish and deploy — a plain zip, ~3 MB:
dotnet publish -c Release -o ./publish
func azure functionapp publish my-pdf-func
# or: zip the publish folder and `az functionapp deployment source config-zip`
Note
First start per instance: ~40 s download + ~90 s extract, performed by PreWarmAsync
off the request path. Outbound HTTPS to github.com and
release-assets.githubusercontent.com must be allowed (relevant for VNet-locked apps —
or self-host the bundle with COBALT_BUNDLE_BASE_URL). Subsequent renders touch the
network not at all.
Azure App Service (Linux)
Code-only zip deployment works on stock App Service Linux — same model as Azure Functions:
dotnet publish -c Release -o ./publish
az webapp deploy --resource-group myRG --name myApp --src-path ./publish --type zip
az webapp config appsettings set -g myRG -n myApp --settings \
COBALT_BUNDLE_CACHE_DIR=/tmp/cobaltbundle \
COBALTPDF_LICENSE="YOUR-LICENSE-KEY"
Call CobaltEngine.PreWarmAsync() at startup so the one-time bundle provisioning happens
before traffic. The /tmp cache-dir setting matters here for the same reason as on
Functions: /home is a slow network share.
Docker
No system packages and no shm_size — the bundle is self-contained. The only requirement
is a base image with glibc ≥ 2.35: mcr.microsoft.com/dotnet/aspnet:8.0 (Debian 12)
and any Ubuntu 22.04+/24.04+ base qualify.
# ── 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 publish MyApp.csproj -c Release --no-restore -o /app/publish
# ── Runtime stage — no apt packages needed ─────────────────────
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
# Optional but recommended: pre-stage the render-bundle tarball at image-build
# time so containers skip the ~262 MB first-start download (the library
# SHA-256-verifies the file and goes straight to extraction).
ENV COBALT_BUNDLE_CACHE_DIR=/opt/cobalt-bundle
ADD https://github.com/CobaltPDF/cobalt-webkit-bundles/releases/download/v1.0.2/cobalt-webkit-bundle-linux-x64-glibc-u2204.tar.gz \
/opt/cobalt-bundle/cobalt-webkit-bundle-linux-x64-glibc-u2204-v1.0.2.tar.gz
ENTRYPOINT ["dotnet", "MyApp.dll"]
Note
Pick the bundle variant by your base image's glibc: u2204 for glibc ≥ 2.35
(Debian 12, Ubuntu 22.04) or u2404 for glibc ≥ 2.38 (Ubuntu 24.04 / noble
images). Skip the ADD lines entirely and the bundle simply downloads on first
start instead.
services:
myapp:
build: { context: ., dockerfile: Dockerfile }
environment:
- COBALTPDF_LICENSE=YOUR-LICENSE-KEY
ports:
- "8080:8080"
No engine preset is needed — on Linux the WebKit edition always renders natively in-process
(ExtraArgs Chromium flags are accepted for API compatibility and ignored).
Important
Allow ptrace in locked-down containers. The self-contained bundle launches WebKit
through proot (it remaps a few hard-coded /usr paths onto the bundled libraries), and
proot uses the Linux ptrace syscall. Docker's default seccomp profile blocks
ptrace — so under a plain docker run / Compose the very first render hangs (you'll
see no error, just a render timeout). Grant the one capability the bundle needs:
# docker compose
services:
myapp:
cap_add: [SYS_PTRACE] # default seccomp allows ptrace once this cap is present
# docker run
docker run --cap-add=SYS_PTRACE ...
# Kubernetes / AKS pod spec
securityContext:
capabilities:
add: ["SYS_PTRACE"]
Stock Azure Functions and App Service already permit this — nothing to add there.
If you would rather not change the security profile at all, base your image on the
prebuilt ghcr.io/cobaltpdf/cobalt-webkit-render image (WebKit is installed normally,
so there is no proot and no ptrace requirement).
Azure Container Apps
Use the WebKit Docker image above. 1 Gi memory per replica is comfortable
(~0.3–0.5 GB typical render tree). Pre-staging the bundle tarball in the image is worth it
here — replicas scale out faster when they skip the download. If first renders hang, add the
SYS_PTRACE capability — see the ptrace note in the Docker section above.
AWS ECS / Fargate
Any Linux container with glibc ≥ 2.35 works — use the WebKit Docker image above, push to
ECR. No shared-memory setting needed. If your task's seccomp profile blocks ptrace and
renders hang, add the SYS_PTRACE Linux capability to the task definition — see the
ptrace note in the Docker section above.
| Resource | Minimum | Recommended |
|---|---|---|
| vCPU | 0.5 | 1.0 |
| Memory | 1 GB | 1–2 GB |
AWS Lambda
Warning
Lambda support is untested guidance, not a validated path. The extracted bundle is
~611 MB, so the default 512 MB /tmp is too small — configure ephemeral storage of at
least 1024 MB and set COBALT_BUNDLE_CACHE_DIR=/tmp/cobaltbundle. Pre-staging the
bundle in the container image (see the Docker tab pattern) avoids per-cold-start
downloads. For sustained volume prefer ECS.
Linux VM / Bare Metal
Nothing to install — any x64 distro with glibc ≥ 2.35 (Ubuntu 22.04+, Debian 12+, Fedora 39+) runs the self-contained bundle out of the box:
dotnet publish -c Release -o ./publish
COBALTPDF_LICENSE="YOUR-LICENSE-KEY" dotnet ./publish/MyApp.dll
The bundle downloads to ~/.cache/CobaltPdfWebKit on first render (override with
COBALT_BUNDLE_CACHE_DIR). Air-gapped hosts can pre-stage the tarball or self-host it
via COBALT_BUNDLE_BASE_URL.
Windows development machines
WebKitGTK is a Linux engine, so on Windows the library renders through a local Linux backend — automatically selected, in this order:
- WSL 2 (preferred — near-native speed). Any distro with glibc ≥ 2.35; the bundle
installs into the distro automatically.
wsl --install Ubuntu-24.04if you have none. - Docker Desktop — used when WSL isn't available (
ForceDocker = trueto require it). Pulls the public dev imageghcr.io/cobaltpdf/cobalt-webkit-render. - Stub mode (
StubModeOnWindows = true, opt-in) — returns placeholder PDFs so controller/integration tests run with no backend at all.
These switches are dev-only: on Linux deployments they are ignored and rendering is always native in-process, so dev configuration can ship to production unchanged.
CI/CD Tips
# No system packages needed — ubuntu-latest runners satisfy the glibc floor.
- name: Publish
run: dotnet publish -c Release -o ./publish
# Integration tests render for real; the bundle downloads once per runner
# (cache COBALT_BUNDLE_CACHE_DIR between runs to skip it).
- name: Cache render bundle
uses: actions/cache@v4
with:
path: ~/.cache/CobaltPdfWebKit
key: cobalt-webkit-bundle-v1
Store the license key in a CI secret for either engine:
env:
COBALTPDF_LICENSE: ${{ secrets.COBALTPDF_LICENSE }}
CobaltEngine.SetLicense(Environment.GetEnvironmentVariable("COBALTPDF_LICENSE")!);
Platform Summary
| Platform | Key setting | Notes |
|---|---|---|
| Windows (development) | (none — auto WSL/Docker) | Production targets Linux |
| Linux VM / bare metal | (none) | glibc ≥ 2.35; zero packages |
| Docker | optional pre-staged tarball | No apt deps, no shm_size; add SYS_PTRACE cap if seccomp-locked |
| Azure App Service (Linux) | COBALT_BUNDLE_CACHE_DIR=/tmp/... |
Stock plan, zip deploy |
| Azure Functions (Linux) | COBALT_BUNDLE_CACHE_DIR=/tmp/... |
Stock plan, zip deploy; B1 batch / EP1+ interactive |
| Azure Container Apps | pre-staged tarball | ≥ 1 Gi memory per replica |
| AWS ECS / Fargate | (none) | Standard Linux container |
| AWS Lambda | ephemeral storage ≥ 1 GB | Untested guidance — prefer ECS |