Table of Contents

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 glibc is 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:

  1. WSL 2 (preferred — near-native speed). Any distro with glibc ≥ 2.35; the bundle installs into the distro automatically. wsl --install Ubuntu-24.04 if you have none.
  2. Docker Desktop — used when WSL isn't available (ForceDocker = true to require it). Pulls the public dev image ghcr.io/cobaltpdf/cobalt-webkit-render.
  3. 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