DEV Community

Andrea Lima
Andrea Lima

Posted on

Why I Stopped Using Local PDF Libraries in my Docker Microservices

If you’ve ever built a .NET or Node.js app that generates PDFs, you know the drill. It works perfectly on your Windows/Mac development machine. Then, you deploy it to a Linux-based Docker container, and boom:

System.Drawing.Common throws a GDI+ error.

The PDF renders, but all the fonts are replaced by weird squares.

The container size doubles because you had to install libgdiplus, fontconfig, and a bunch of X11 dependencies.

The "Dependency Hell" of PDF Engines
Most popular libraries (like iText, Puppeteer, or older versions of QuestPDF) rely on underlying OS graphics libraries. In a microservices world, this is a nightmare:

Bloated Images: Your lightweight Alpine image is no longer lightweight.

Memory Leaks: Many wrappers for PDF generation aren't great at disposing of unmanaged resources.

Inconsistency: A table that looks perfect in Dev might have slight alignment issues in your CI/CD pipeline due to different library versions in the Linux distro.

Moving to a Stateless, API-First Approach
Recently, I decided to offload this entire "heavy lifting" to a dedicated microservice. Instead of fighting with libgdiplus in every new project, I built a stateless API: SwiftInvoice.

The logic is simple: Your app shouldn't be a printing press. It should just handle data.

By using an external API, my Dockerfiles went from this:

# The "Heavy" way
FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y libgdiplus libx11-6 && ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll
# ... more font configurations ...
Enter fullscreen mode Exit fullscreen mode

To this:

# The "Clean" way
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
# Just your app. The API handles the rest.
Enter fullscreen mode Exit fullscreen mode

Quick Win: Professional Invoices in Seconds
I integrated Native PIX support (for Brazilian markets) and I18n (EN, PT, ES) directly into the engine. You just POST a JSON, and you get a clean, print-ready PDF stream back.

Example (Node.js):

JavaScript

const axios = require('axios');
const fs = require('fs');

const generateInvoice = async () => {
    const options = {
        method: 'POST',
        url: 'https://swiftinvoice.p.rapidapi.com/GenerateInvoice',
        headers: {
            'content-type': 'application/json',
            'X-RapidAPI-Key': 'YOUR_RAPIDAPI_KEY', // Get it at rapidapi.com
            'X-RapidAPI-Host': 'swiftinvoice.p.rapidapi.com'
        },
        data: {
            // Document Info
            InvoiceNumber: "2026-001",
            Language: "pt-BR", // Supports pt-BR, en-US, es-ES
            Currency: "BRL",
            IssueDate: new Date().toISOString(),
            DueDate: "2026-04-15T00:00:00Z",
            Template: "blue", // Design theme
            IsDraft: false,

            // Sender Info
            SenderName: "Your Company Name",
            SenderCity: "Rio de Janeiro",
            SenderDocument: "00.000.000/0001-00", // CNPJ/CPF
            LogoUrl: "https://yourlink.com/logo.png",

            // Recipient Info
            RecipientName: "Client XYZ",
            RecipientAddress: "Main Street, 123",
            CustomerEmail: "client@email.com",

            // Items & Billing
            Items: [
                { Description: "Software Development", Quantity: 1, UnitPrice: 1500.00 },
                { Description: "Cloud Maintenance", Quantity: 1, UnitPrice: 50.00 }
            ],
            TaxRate: 0,
            DiscountAmount: 0,

            // Payment & PIX
            PixKey: "your-pix-key@provider.com", // Native QR Code generation
            BankName: "Banco do Brasil",
            Agency: "0001",
            AccountNumber: "12345-6"
        },
        responseType: 'arraybuffer'
    };

    try {
        const response = await axios.request(options);
        fs.writeFileSync('invoice.pdf', response.data);
        console.log('✅ PDF successfully generated: invoice.pdf');
    } catch (error) {
        console.error('❌ Error details:', error.response ? error.response.data.toString() : error.message);
    }
};

generateInvoice();

Enter fullscreen mode Exit fullscreen mode

Update: I've just released a repository with full boilerplate examples in C#, Node.js, Python, PHP, and Go! Check it out here.

Conclusion
Focus on your business logic, not on debugging Linux font rendering. If you want to try this approach, I’ve made SwiftInvoice available on RapidAPI with a Free Tier for developers to experiment with.

👉 Check it out here: SwiftInvoice on RapidAPI

I'm curious: How are you handling PDF generation in your current stack? Are you still using local libraries or have you moved to a service-based model?

Top comments (0)