Container font provisioning is a GroupDocs.Signature requirement for .NET that decides whether text signatures work at all once your service leaves a developer machine. The library resolves a font family through the platform, and it does not substitute when the family is absent: naming one that is not installed raises Sign document error: Font <name> was not found and no document is written. Omitting SignatureFont does not help, because the library then asks for its own default, Times New Roman, and fails identically.
That matters because base images are not desktops. Measured on the images this guide’s sample runs in: mcr.microsoft.com/dotnet/runtime:8.0 ships zero font files, eclipse-temurin:17-jre ships 8 (DejaVu), node:18-bookworm ships 6, and python:3.11-slim ships zero. On the .NET runtime image, a text signature therefore fails outright until you add a font layer. There is no code-level workaround.
This guide covers the integration side of that: what to install, how to pick a family at run time instead of hard-coding one, how to degrade when a script has no font, and how to prove the result rather than assume it.
Run the same image twice, once with the font layer and once without, and compare the log. The sample repository ships both Dockerfiles for exactly that reason.
Prerequisites
The sample targets net8.0 and runs on mcr.microsoft.com/dotnet/runtime:8.0, with GroupDocs.Signature 26.6.0 or later. A licence is optional for following along: without one the run still signs, in evaluation mode, and adds trial text to the page.
Core Concepts
A family name is what SignatureFont.FamilyName takes, and it is not the file name. Debian’s fonts-noto-cjk package installs NotoSansCJK-Regular.ttc, whose family is Noto Sans CJK JP. Detecting fonts by scanning /usr/share/fonts for file names therefore both misses fonts that are present and reports families that will not resolve.
Resolution by probing is the portable alternative: attempt a throwaway signature per candidate family and keep the first that does not throw. It asks the library the same question the real call will ask, so the answer cannot be wrong for the reason above.
Two failure classes need different handling. No Latin font at all means the image cannot sign, which is fatal. No CJK font means non-Latin text cannot be embedded, which is a skip plus a warning, not a crash.
Which fonts do I actually need to install?
One is the minimum: fonts-dejavu-core makes Latin, Greek and Cyrillic signing work. Add fonts-liberation when your documents reference Arial, Times New Roman or Courier New, since it supplies metric-compatible stand-ins under resolvable names. Add fonts-noto-cjk for Chinese, Japanese or Korean text. Install fontconfig alongside them for the resolver itself and for fc-list when debugging.
Integration Patterns
Pattern 1: Inventory first, resolve second
Log what the image has before asking for anything. The inventory turns “font not found” into a diagnosis, because a count of zero and a count of 24 point at completely different fixes. The scan deliberately avoids System.Drawing: System.Drawing.Common is Windows-only from .NET 7 onward and throws on Linux, which is its own common container failure.
The sample uses DejaVu Sans, Liberation Sans, Arial, Verdana for Latin and puts Noto Sans CJK JP first for CJK, so a container resolves on the first probe and a Windows developer box falls through to Arial.
Pattern 2: Probe with a throwaway signature
The probe is a real Sign call into a temporary file, which is why its answer is trustworthy. The scratch file is always deleted, so probing never touches your output directory.
Then read the file back. Rendering CJK as empty boxes is not an exception, so the search is the only step that distinguishes a real signature from a visually broken one:
install the font layer; resolve the family at run time instead of hard-coding
Same error with no font set
GroupDocs fell back to Times New Roman, also absent
same fix; omitting SignatureFont is not a workaround on a fontless image
CultureNotFoundException: ... en-US is an invalid culture identifier
InvariantGlobalization=true in the csproj
keep globalization on and install ICU in the image; SignatureSettings builds CultureInfo("en-US")
CJK signature written but shows as boxes
Latin font resolved, CJK font missing
install fonts-noto-cjk and check the read-back, not the return value
Treat a fatal font failure as an exit code, not a warning. The sample catches GroupDocsSignatureException around the real signing call, prints the minimum fix (apt-get install -y fonts-dejavu-core), and returns 3.
Production Readiness Checklist
The runtime image installs at least fontconfig and fonts-dejavu-core
Font resolution runs once at startup, logs the resolved families, and fails the container when no Latin family resolves
Non-Latin text has fonts-noto-cjk installed and the read-back is asserted
InvariantGlobalization is not set to true, and the licence is mounted rather than baked in
Frequently Asked Questions
Q: Can I ship a font file with the application instead of installing packages?
A: You can put font files in a directory the image reads, but the family still has to resolve through the platform, so fc-cache and a font directory the resolver knows about are what make it work. Installing the Debian packages is simply the shortest path to that.
Q: Why probe at all, when I know which fonts my Dockerfile installs?
A: Because the same code also runs on a developer machine, in CI, and on the next base image someone bumps. I kept a hard-coded DejaVu Sans for a while and it worked until a colleague ran the same service on Windows, where that family is not installed and the run died at the first signature.
Q: Does this apply to image and barcode signatures too?
A: No. The font requirement is specific to text-based signatures, which is where a family name is resolved. Image, barcode and QR signatures do not need a font installed, though a stamp signature with a text label does.
Conclusion
Install one font at minimum, resolve the family by asking the library, skip what cannot be embedded, and read the result back before calling the job successful. The sample repository builds both images so the difference is reproducible in two commands rather than a claim in a document.