💡 Volledig werkend voorbeeld beschikbaar op GitHub:
python-linux-container-pdf-signing

Introductie

Het script werkt lokaal. Je containeriseert het op python:3.11-slim, en het faalt bij import groupdocs.signature. Je lost dat op, en het faalt opnieuw bij de eerste handtekening. Geen van beide fouten vermeldt wat er daadwerkelijk ontbreekt.

Containerondertekening met Python is een GroupDocs.Signature‑workflow die twee provisioning‑lagen vereist in plaats van één: de .NET‑runtime‑bibliotheken waarop de binding is gebouwd, en de lettertypen die elke teksthandtekening moet kunnen weergeven. Deze tutorial bouwt beide lagen, daarna het script dat een lettertype‑familie tijdens runtime bepaalt in plaats van er één hard‑gecodeerd te gebruiken, zodat dezelfde code werkt in de container en op de machine waarop je het schreef.

Waarom beide lagen belangrijk zijn

GroupDocs.Signature voor Python is een .NET‑binding, dus libicu en een OpenSSL 1.1‑compatibele bibliotheek moeten aanwezig zijn voordat een import slaagt. Dat is laag één, en het is goed gedocumenteerd in Running in Docker.

De reden dat de twee lagen door elkaar gehaald worden, is dat beide falen op momenten die direct bij een import liggen en geen van beide fouten de oorzaak benoemt. Een ontbrekende libssl1.1 geeft je een loader‑fout over een shared object; een ontbrekend lettertype geeft je een ondertekeningsfout verpakt in een proxy‑exception. Geen van beide zegt “je basis‑image is te klein”, wat in feite de betekenis is.

Laag twee zijn lettertypen, en dat is de laag die mensen verrast. python:3.11-slim bevat geen enkele lettertype‑file. GroupDocs.Signature vervangt een ontbrekende familie niet – het benoemen van een niet‑geïnstalleerde familie veroorzaakt een fout, en er wordt niets geschreven – en het wissen van het lettertype is ook geen oplossing, omdat de bibliotheek dan om zijn eigen standaard vraagt en op dezelfde manier faalt. Op een image zonder lettertypen is een teksthandtekening simpelweg onmogelijk.

Vereisten

Python 3.11 (de wheel‑limiet ligt onder CPython 3.14) en groupdocs-signature-net==26.1. Docker als je beide fouten opzettelijk wilt zien, wat tien minuten waard is.

Installatie

pip install groupdocs-signature-net==26.1

Stap 1 - Bouw de .NET‑laag

libssl1.1 zit niet in bookworm, dus die komt van een vastgezette Debian‑snapshot:

ENV SNAPSHOT_DATE=20220328T000000Z
RUN echo "deb [trusted=yes] http://snapshot.debian.org/archive/debian/${SNAPSHOT_DATE} bullseye main" \
        > /etc/apt/sources.list.d/debian-archive.list \
    && apt-get -o Acquire::Check-Valid-Until=false update \
    && apt-get install -y --no-install-recommends \
        libicu67 \
        libssl1.1 \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

Belangrijke punten:

  • Deze laag maakt alleen de import werkend; ze zegt niets over lettertypen.
  • Het vastzetten van de snapshot‑datum houdt de build reproduceerbaar wanneer het archief verdergaat.

Stap 2 - Bouw de lettertype‑laag

Vier pakketten, gehouden als hun eigen laag zodat ze kunnen worden uitgecommentarieerd om de fout te reproduceren:

RUN apt-get update && apt-get install -y --no-install-recommends \
        fontconfig \
        fonts-dejavu-core \
        fonts-liberation \
        fonts-noto-cjk \
    && fc-cache -f \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

fontconfig is de resolver en geeft je fc-list. fonts-dejavu-core levert het minimale Latin, Greek en Cyrillic. fonts-liberation dekt documenten die naar Arial of Times New Roman verwijzen. fonts-noto-cjk dekt Chinees, Japans en Koreaans.

Stap 3 - Vraag de bibliotheek welke familie hij kan gebruiken

Scannen van /usr/share/fonts op een bestandsnaam lijkt equivalent, maar is dat niet: fonts-noto-cjk installeert NotoSansCJK-Regular.ttc, waarvan de familienaam Noto Sans CJK JP is. Het draagbare antwoord is een probe – een echte handtekening in een tijdelijk bestand – waarbij de fout wordt omgezet in een waarde:

with signature.Signature(source_path) as sign:
    options = TextSignOptions()
    options.text = "probe"
    options.left = 10
    options.top = 10
    options.width = 60
    options.height = 20
    font = SignatureFont()
    font.family_name = family_name
    font.size = 10.0
    options.font = font
    sign.sign(scratch, [options])
return None

Let goed op font.size = 10.0. De binding zet de grootte om naar een .NET‑float en wijst een int af met numeric argument expected, got 'int'. Omdat dat binnen de probe gebeurt, faalt elke kandidaat‑familie en ziet de output er precies uit als een image zonder lettertype. Ik heb drie lettertype‑pakketten toegevoegd aan een image die ze al had, voordat ik de letterlijke fout opmerkte.

De oplossing wordt dan een lus:

for candidate in candidates:
    if try_family(source_path, candidate) is None:
        return candidate
return None

Stap 4 - Onderteken wat is opgelost, verifieer wat je hebt ondertekend

De Latin‑familie is vereist, de CJK‑familie optioneel:

with signature.Signature(source_path) as sign:
    options = [build_text_options(LATIN_TEXT, latin_family, 50)]
    if cjk_family:
        options.append(build_text_options(CJK_TEXT, cjk_family, 120))
    result = sign.sign(output_path, options)
    return len(result.succeeded)

Verifieer vervolgens, want CJK die als lege vakjes wordt gerenderd, veroorzaakt geen fout:

options = TextVerifyOptions()
options.text = expected_text
options.match_type = gsd.TextMatchType.CONTAINS
options.all_pages = True
result = sign.verify(options)

CONTAINS is opzettelijk: in evaluatiemodus voegt de bibliotheek proeftekst toe aan de pagina, en een exacte overeenkomst zou een perfect goed document als mislukt rapporteren.

Wat als de documentatie zegt dat Python beperkte Linux‑ondersteuning heeft?

De pagina Running in Docker somt Linux‑klare Python‑pakketten op en laat Signature weg. Op groupdocs-signature-net==26.1 heeft dit voorbeeld ondertekend en geverifieerd binnen python:3.11-slim, inclusief CJK, met beide lagen geïnstalleerd. Beschouw de lijst als verouderd in plaats van als een blokkade, en controleer met je eigen versie voordat je een deployment vastlegt.

Toepassingen in de praktijk

Een facturatieservice die een goedkeuringslijn op gegenereerde PDF’s plaatst, heeft precies dit nodig: de .NET‑laag, één Latin‑lettertype, en een opstart‑resolutie‑check. Die check is wat een slechte deployment verandert in een container die weigert te starten, in plaats van een wachtrij facturen die één voor één stilletjes falen. Een documentportaal dat klantnamen in elk script accepteert, heeft ook het CJK‑pakket nodig, plus de verificatiestap, want dat is het enige dat staat tussen een gerenderde doos en een ondertekende naam.

Waar de resolutiecontrole thuishoort

Plaats het waar het één keer per proces wordt uitgevoerd: een module‑level call, een FastAPI‑lifespan‑handler, een Django AppConfig.ready, of de eerste regels van de main‑functie van een worker. Twee waarden komen eruit, de Latin‑familie en de CJK‑familie, en beide behoren in de opstart‑log naast het aantal lettertypen.

Die plaatsing doet meer dan alleen probe‑tijd besparen. Het verplaatst de fout van request‑afhandeling (waar het één klant‑probleem is en een stack‑trace die niemand leest) naar de opstart, waar het een deployment is die niet opkomt en iemand al kijkt. Een container die afsluit met “no usable font family, install fonts-dejavu-core” heeft helemaal geen debugging nodig.

Veelvoorkomende problemen oplossen

import groupdocs.signature faalt
De .NET‑laag ontbreekt of de snapshot‑repository was onbereikbaar tijdens de build. Dit is laag één, en heeft niets met lettertypen te maken. Controleer het build‑log voor de apt‑stap voordat je enige ondertekeningscode aanraakt, want een mislukte snapshot‑fetch stopt de image‑build niet.

Elke kandidaat‑lettertype faalt, maar fc-list toont lettertypen
Controleer font.size op een int voordat je meer pakketten toevoegt.

De handtekening is er, maar de CJK‑tekst is vakjes
fonts-noto-cjk ontbreekt. De handtekening werd geschreven met een familie die geen glyphs heeft voor die code‑points, daarom bestaat de verificatiestap: die faalt precies in dit geval, waar ondertekenen succes rapporteerde.

Wat de twee images daadwerkelijk afdrukken

Voer beide uit en lees de eerste vier regels. De image zonder lettertypen meldt font files on disk: 0, beide resolutielijnen als (none), de opzettelijke missing‑font‑fout, en sluit af met exit‑code 3 en de minimale fix die wordt geprint. De geprovisioneerde image meldt een niet‑nul aantal lettertypen, DejaVu Sans voor Latin en Noto Sans CJK JP voor CJK, twee toegepaste handtekeningen, en beide teksten geverifieerd.

Dat paar output is het artefact dat je moet bewaren. Plak het in je deployment‑notities en de volgende persoon die de basis‑image wijzigt heeft een referentie voor hoe een gezonde container eruitziet, zonder dat hij/zij fontconfig hoeft te begrijpen.

Conclusie

Twee lagen en één probe. Installeer de .NET‑afhankelijkheden, installeer minimaal fontconfig en DejaVu, los de familie op door te vragen in plaats van aan te nemen, en verifieer de output voordat je het werk afrekent. Het is weinig code, en alles is van die soort dingen die achteraf logisch lijken en in een traceback onzichtbaar zijn. De voorbeeld‑repository levert beide Dockerfiles, dus het verschil tussen een werkende image en een kapotte is één build‑stap.

Aanvullende bronnen