💡 Full working example available on GitHub:
python-linux-container-pdf-signing

Introdução

O script funciona localmente. Você o containeriza em python:3.11-slim e ele falha ao executar import groupdocs.signature. Você corrige isso, e ele falha novamente na primeira assinatura. Nenhum dos erros menciona o que realmente está faltando.

A assinatura em contêiner com Python é um fluxo de trabalho do GroupDocs.Signature que requer duas camadas de provisionamento em vez de uma: as bibliotecas de tempo de execução do .NET nas quais a ligação foi construída e as fontes que toda assinatura de texto precisa para ser renderizada. Este tutorial constrói ambas, depois o script que resolve uma família de fontes em tempo de execução em vez de codificar uma fixa, de modo que o mesmo código funcione no contêiner e na máquina onde foi escrito.

Why Both Layers Matter

GroupDocs.Signature for Python é uma ligação .NET, portanto libicu e uma biblioteca compatível com OpenSSL 1.1 precisam existir antes que qualquer importação seja bem‑sucedida. Essa é a camada um, e está bem documentada em Running in Docker.

A razão pela qual as duas camadas são confundidas é que ambas falham em momentos próximos à importação e nenhum erro nomeia sua causa. Um libssl1.1 ausente gera um erro de carregamento sobre um objeto compartilhado; uma fonte ausente gera um erro de assinatura encapsulado em uma exceção proxy. Nenhum diz “sua imagem base é muito pequena”, que é o que ambas realmente significam.

A camada dois são as fontes, e é a que surpreende as pessoas. python:3.11-slim não contém nenhum arquivo de fonte. GroupDocs.Signature não substitui uma família ausente – nomear uma que não está instalada gera exceção, e nada é escrito – e limpar a fonte não é uma solução alternativa, porque a biblioteca então solicita sua própria fonte padrão e falha de forma idêntica. Em uma imagem sem fontes, uma assinatura de texto é simplesmente impossível.

Pré‑requisitos

Python 3.11 (a roda abaixo tem limite inferior em CPython 3.14) e groupdocs-signature-net==26.1. Docker se você quiser ver ambas as falhas de propósito, o que leva cerca de dez minutos.

Instalação

pip install groupdocs-signature-net==26.1

Etapa 1 – Construir a camada .NET

libssl1.1 não está no Bookworm, então ele vem de um snapshot Debian fixado:

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/*

Pontos principais:

  • Esta camada apenas faz a importação funcionar; não diz nada sobre fontes.
  • Fixar a data do snapshot mantém a construção reproduzível quando o arquivo muda.

Etapa 2 – Construir a camada de fontes

Quatro pacotes, mantidos como camada própria para que possam ser comentados e reproduzir a falha:

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 é o resolvedor e fornece fc-list. fonts-dejavu-core cobre o mínimo latino, grego e cirílico. fonts-liberation cobre documentos que referenciam Arial ou Times New Roman pelo nome. fonts-noto-cjk cobre chinês, japonês e coreano.

Etapa 3 – Perguntar à biblioteca qual família ela pode usar

Escanear /usr/share/fonts por nome de arquivo parece equivalente e não é: fonts-noto-cjk instala NotoSansCJK-Regular.ttc, cujo nome de família é Noto Sans CJK JP. A resposta portátil é uma sondagem – uma assinatura real em um arquivo temporário – com a falha convertida em um valor:

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

Observe font.size = 10.0. A ligação mapeia o tamanho para um float .NET e rejeita um int com a mensagem numeric argument expected, got 'int'. Como isso acontece dentro da sondagem, toda família candidata falha e a saída parece exatamente como uma imagem sem fontes. Eu adicionei três pacotes de fontes a uma imagem que já os continha antes de perceber o literal.

A resolução então se torna um loop:

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

Etapa 4 – Assinar o que foi resolvido, verificar o que foi assinado

A família latina é obrigatória, a CJK opcional:

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)

Depois verifique, porque CJK renderizado como caixas vazias não gera exceção:

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

CONTAINS é deliberado: em modo de avaliação a biblioteca adiciona texto de teste à página, e uma correspondência exata reportaria um documento perfeitamente bom como falho.

E os documentos que dizem que Python tem suporte Linux limitado?

A página Running in Docker lista pacotes Python prontos para Linux e deixa o Signature de fora. No groupdocs-signature-net==26.1 este exemplo assinou e verificou dentro de python:3.11-slim, incluindo CJK, com ambas as camadas instaladas. Considere a lista desatualizada em vez de um bloqueio, e confirme com sua própria versão antes de comprometer um deployment.

Aplicações no Mundo Real

Um serviço de faturamento que carimba uma linha de aprovação em PDFs gerados precisa exatamente disso: a camada .NET, uma fonte latina e uma verificação de resolução na inicialização. A verificação é o que transforma um deployment ruim em um contêiner que se recusa a iniciar, ao invés de uma fila de faturas que falham silenciosamente uma a uma. Um portal de documentos que aceita nomes de clientes em qualquer escrita precisa também do pacote CJK, além da etapa de verificação, porque é a única coisa que impede que uma caixa renderizada seja aceita como nome assinado.

Onde a verificação de resolução deve ficar

Coloque-a onde quer que seja executada uma vez por processo: uma chamada ao nível de módulo, um handler de lifespan do FastAPI, um AppConfig.ready do Django, ou as primeiras linhas do main de um worker. Dois valores são retornados, a família latina e a família CJK, e ambos devem aparecer no log de inicialização ao lado da contagem de fontes.

Essa colocação faz mais do que economizar tempo de sondagem. Ela move a falha do tratamento de requisição – problema de um cliente e stack trace que ninguém lê – para a inicialização, onde se torna um deployment que não subiu e alguém já está observando. Um contêiner que sai com “no usable font family, install fonts-dejavu-core” não precisa de depuração alguma.

Solução de Problemas – Problemas Comuns

import groupdocs.signature falha
A camada .NET está ausente ou o repositório de snapshot estava inacessível durante a build. Esta é a camada um, e não tem nada a ver com fontes. Verifique o log da build para a etapa apt antes de tocar em qualquer código de assinatura, pois uma falha ao buscar o snapshot não impede a imagem de ser construída.

Toda fonte candidata falha, mas fc-list mostra fontes
Verifique se font.size está como int antes de adicionar mais pacotes.

A assinatura aparece, mas o texto CJK são caixas
fonts-noto-cjk está ausente. A assinatura foi escrita com uma família que não tem glifos para esses pontos de código, por isso a etapa de verificação existe: ela falha exatamente nesse caso, onde a assinatura relatou sucesso.

O que as duas imagens realmente imprimem

Execute ambas e leia as quatro primeiras linhas. A imagem sem fontes relata font files on disk: 0, ambas as linhas de resolução como (none), o erro deliberado de fonte ausente e sai com código 3 mostrando a correção mínima. A imagem provisionada relata contagem de fontes diferente de zero, DejaVu Sans para Latin e Noto Sans CJK JP para CJK, duas assinaturas aplicadas e ambos os textos verificados.

Esse par de saídas é o artefato que vale a pena guardar. Cole‑o nas notas de deployment e a próxima pessoa que mudar a imagem base terá uma referência do que é um contêiner saudável, sem precisar entender fontconfig.

Conclusão

Duas camadas e uma sondagem. Instale as dependências .NET, instale ao menos fontconfig e DejaVu, resolva a família perguntando em vez de assumir, e verifique a saída antes de declarar o trabalho concluído. Nada disso é muito código, e tudo isso é o tipo de coisa que parece óbvia em retrospectiva e invisível em um traceback. O repositório de exemplo entrega ambos Dockerfiles, de modo que a diferença entre uma imagem funcional e uma quebrada está a um build de distância.

Recursos Adicionais