How to Migrate from Gotenberg to IronPDF in C#
Migrating from Gotenberg to IronPDF transforms your .NET PDF workflow from a Docker-based microservice architecture with HTTP API calls to an in-process native C# library. This guide provides a comprehensive, step-by-step migration path that eliminates infrastructure overhead, network latency, and container management complexity for professional .NET developers.
Why Migrate from Gotenberg to IronPDF
The Gotenberg Architecture Problem
Gotenberg is a Docker-based microservice architecture for PDF generation. While powerful and flexible, it introduces significant complexity for C# applications:
Infrastructure Overhead: Requires Docker, container orchestration (Kubernetes/Docker Compose), service discovery, and load balancing. Every deployment becomes more complex.
Network Latency: Every PDF operation requires an HTTP call to a separate service—adding 10-100ms+ per request. This latency compounds quickly in high-volume scenarios.
Cold Start Issues: Container startup can add 2-5 seconds to first requests. Every pod restart, every scale-up event, and every deployment triggers cold starts.
Operational Complexity: You must manage container health, scaling, logging, and monitoring as separate concerns from your main application.
Multipart Form Data: Every request requires constructing multipart/form-data payloads—verbose, error-prone, and tedious to maintain.
Failure Points: Network timeouts, service unavailability, and container crashes all become your responsibility to handle.
- Version Management: Gotenberg images update separately from your application; API changes can break integrations unexpectedly.
Gotenberg vs IronPDF Comparison
| Aspect | Gotenberg | IronPDF |
|---|---|---|
| Deployment | Docker container + orchestration | Single NuGet package |
| Architecture | Microservice (REST API) | In-process library |
| Latency per request | 10-100ms+ (network round-trip) | < 1ms overhead |
| Cold start | 2-5 seconds (container init) | 1-2 seconds (first render only) |
| Infrastructure | Docker, Kubernetes, load balancers | None required |
| Failure Modes | Network, container, service failures | Standard .NET exceptions |
| API Style | REST multipart/form-data | Native C# method calls |
| Scaling | Horizontal (more containers) | Vertical (in-process) |
| Debugging | Distributed tracing needed | Standard debugger |
| Version Control | Container image tags | NuGet package versions |
For teams planning .NET 10 and C# 14 adoption through 2025 and 2026, IronPDF provides a future-proof foundation with zero infrastructure dependencies that integrates natively with modern .NET patterns.
Migration Complexity Assessment
Estimated Effort by Feature
| Feature | Migration Complexity | Notes |
|---|---|---|
| HTML to PDF | Very Low | Direct method replacement |
| URL to PDF | Very Low | Direct method replacement |
| Custom Paper Size | Low | Property-based configuration |
| Margins | Low | Unit conversion (inches → mm) |
| PDF Merging | Low | Static method call |
| Headers/Footers | Medium | Different placeholder syntax |
| Wait Delays | Low | String to milliseconds |
| PDF/A Conversion | Low | Method call instead of parameter |
Paradigm Shift
The fundamental shift in this Gotenberg migration is from HTTP API calls with multipart form data to native C# method calls:
Gotenberg: HTTP POST multipart/form-data to Docker container
IronPDF: Direct method calls on C# objectsBefore You Start
Prerequisites
- .NET Version: IronPDF supports .NET Framework 4.6.2+ and .NET Core 3.1+ / .NET 5/6/7/8/9+
- License Key: Obtain your IronPDF license key from ironpdf.com
- Plan Infrastructure Removal: Document Gotenberg containers for decommissioning post-migration
Identify All Gotenberg Usage
# Find direct HTTP calls to Gotenberg
grep -r "gotenberg\|/forms/chromium\|/forms/libreoffice\|/forms/pdfengines" --include="*.cs" .
# Find GotenbergSharpApiClient usage
grep -r "GotenbergSharpClient\|Gotenberg.Sharp\|ChromiumRequest" --include="*.cs" .
# Find Docker/Kubernetes Gotenberg configuration
grep -r "gotenberg/gotenberg\|gotenberg:" --include="*.yml" --include="*.yaml" .# Find direct HTTP calls to Gotenberg
grep -r "gotenberg\|/forms/chromium\|/forms/libreoffice\|/forms/pdfengines" --include="*.cs" .
# Find GotenbergSharpApiClient usage
grep -r "GotenbergSharpClient\|Gotenberg.Sharp\|ChromiumRequest" --include="*.cs" .
# Find Docker/Kubernetes Gotenberg configuration
grep -r "gotenberg/gotenberg\|gotenberg:" --include="*.yml" --include="*.yaml" .NuGet Package Changes
# Remove Gotenberg client (if using)
dotnet remove package Gotenberg.Sharp.API.Client
# Install IronPDF
dotnet add package IronPdf# Remove Gotenberg client (if using)
dotnet remove package Gotenberg.Sharp.API.Client
# Install IronPDF
dotnet add package IronPdfQuick Start Migration
Step 1: Update License Configuration
Before (Gotenberg):
Gotenberg requires no license but requires Docker infrastructure with container URLs.
private readonly string _gotenbergUrl = "http://localhost:3000";private readonly string _gotenbergUrl = "http://localhost:3000";After (IronPDF):
// Set once at application startup
IronPdf.License.LicenseKey = "YOUR-IRONPDF-LICENSE-KEY";// Set once at application startup
IronPdf.License.LicenseKey = "YOUR-IRONPDF-LICENSE-KEY";Step 2: Update Namespace Imports
// Before (Gotenberg)
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
// After (IronPDF)
using IronPdf;
using IronPdf.Rendering;// Before (Gotenberg)
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
// After (IronPDF)
using IronPdf;
using IronPdf.Rendering;Complete API Reference
Gotenberg Endpoint to IronPDF Mapping
| Gotenberg Route | IronPDF Equivalent | Notes |
|---|---|---|
POST /forms/chromium/convert/html | ChromePdfRenderer.RenderHtmlAsPdf() | HTML string to PDF |
POST /forms/chromium/convert/url | ChromePdfRenderer.RenderUrlAsPdf() | URL to PDF |
POST /forms/pdfengines/merge | PdfDocument.Merge() | Merge multiple PDFs |
POST /forms/pdfengines/convert | pdf.SaveAs() with settings | PDF/A conversion |
GET /health | N/A | No external service needed |
Form Parameter to RenderingOptions Mapping
| Gotenberg Parameter | IronPDF Property | Conversion Notes |
|---|---|---|
paperWidth (inches) | RenderingOptions.PaperSize | Use enum or custom size |
paperHeight (inches) | RenderingOptions.PaperSize | Use enum or custom size |
marginTop (inches) | RenderingOptions.MarginTop | Multiply by 25.4 for mm |
marginBottom (inches) | RenderingOptions.MarginBottom | Multiply by 25.4 for mm |
printBackground | RenderingOptions.PrintHtmlBackgrounds | Boolean |
landscape | RenderingOptions.PaperOrientation | Landscape enum |
waitDelay | RenderingOptions.RenderDelay | Convert to milliseconds |
Code Migration Examples
Example 1: Basic HTML to PDF
Before (Gotenberg):
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
class GotenbergExample
{
static async Task Main()
{
var gotenbergUrl = "http://localhost:3000/forms/chromium/convert/html";
using var client = new HttpClient();
using var content = new MultipartFormDataContent();
var html = "<html><body><h1>Hello from Gotenberg</h1></body></html>";
content.Add(new StringContent(html), "files", "index.html");
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("output.pdf", pdfBytes);
Console.WriteLine("PDF generated successfully");
}
}using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
class GotenbergExample
{
static async Task Main()
{
var gotenbergUrl = "http://localhost:3000/forms/chromium/convert/html";
using var client = new HttpClient();
using var content = new MultipartFormDataContent();
var html = "<html><body><h1>Hello from Gotenberg</h1></body></html>";
content.Add(new StringContent(html), "files", "index.html");
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("output.pdf", pdfBytes);
Console.WriteLine("PDF generated successfully");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using System;
using IronPdf;
class IronPdfExample
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello from IronPDF</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
Console.WriteLine("PDF generated successfully");
}
}// NuGet: Install-Package IronPdf
using System;
using IronPdf;
class IronPdfExample
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello from IronPDF</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
Console.WriteLine("PDF generated successfully");
}
}The difference is substantial: Gotenberg requires constructing an HttpClient, building MultipartFormDataContent, making an async HTTP POST to a running Docker container, and handling the byte array response. IronPDF reduces this to three lines with a ChromePdfRenderer method call—no network overhead, no container dependency, no async complexity. See the HTML to PDF documentation for additional rendering options.
Example 2: URL to PDF Conversion
Before (Gotenberg):
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
class GotenbergUrlToPdf
{
static async Task Main()
{
var gotenbergUrl = "http://localhost:3000/forms/chromium/convert/url";
using var client = new HttpClient();
using var content = new MultipartFormDataContent();
content.Add(new StringContent("https://example.com"), "url");
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("webpage.pdf", pdfBytes);
Console.WriteLine("PDF from URL generated successfully");
}
}using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
class GotenbergUrlToPdf
{
static async Task Main()
{
var gotenbergUrl = "http://localhost:3000/forms/chromium/convert/url";
using var client = new HttpClient();
using var content = new MultipartFormDataContent();
content.Add(new StringContent("https://example.com"), "url");
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("webpage.pdf", pdfBytes);
Console.WriteLine("PDF from URL generated successfully");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using System;
using IronPdf;
class IronPdfUrlToPdf
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://example.com");
pdf.SaveAs("webpage.pdf");
Console.WriteLine("PDF from URL generated successfully");
}
}// NuGet: Install-Package IronPdf
using System;
using IronPdf;
class IronPdfUrlToPdf
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://example.com");
pdf.SaveAs("webpage.pdf");
Console.WriteLine("PDF from URL generated successfully");
}
}The Gotenberg approach requires a different endpoint (/forms/chromium/convert/url), building multipart content with the URL as a form field, and handling async HTTP responses. IronPDF's RenderUrlAsPdf() method accepts the URL directly and returns a PdfDocument object synchronously. Learn more about URL to PDF conversion.
Example 3: Custom Paper Size and Margins
Before (Gotenberg):
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
class GotenbergCustomSize
{
static async Task Main()
{
var gotenbergUrl = "http://localhost:3000/forms/chromium/convert/html";
using var client = new HttpClient();
using var content = new MultipartFormDataContent();
var html = "<html><body><h1>Custom Size PDF</h1></body></html>";
content.Add(new StringContent(html), "files", "index.html");
content.Add(new StringContent("8.5"), "paperWidth");
content.Add(new StringContent("11"), "paperHeight");
content.Add(new StringContent("0.5"), "marginTop");
content.Add(new StringContent("0.5"), "marginBottom");
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("custom-size.pdf", pdfBytes);
Console.WriteLine("Custom size PDF generated successfully");
}
}using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
class GotenbergCustomSize
{
static async Task Main()
{
var gotenbergUrl = "http://localhost:3000/forms/chromium/convert/html";
using var client = new HttpClient();
using var content = new MultipartFormDataContent();
var html = "<html><body><h1>Custom Size PDF</h1></body></html>";
content.Add(new StringContent(html), "files", "index.html");
content.Add(new StringContent("8.5"), "paperWidth");
content.Add(new StringContent("11"), "paperHeight");
content.Add(new StringContent("0.5"), "marginTop");
content.Add(new StringContent("0.5"), "marginBottom");
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("custom-size.pdf", pdfBytes);
Console.WriteLine("Custom size PDF generated successfully");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using System;
using IronPdf;
using IronPdf.Rendering;
class IronPdfCustomSize
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 50;
renderer.RenderingOptions.MarginBottom = 50;
var html = "<html><body><h1>Custom Size PDF</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("custom-size.pdf");
Console.WriteLine("Custom size PDF generated successfully");
}
}// NuGet: Install-Package IronPdf
using System;
using IronPdf;
using IronPdf.Rendering;
class IronPdfCustomSize
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 50;
renderer.RenderingOptions.MarginBottom = 50;
var html = "<html><body><h1>Custom Size PDF</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("custom-size.pdf");
Console.WriteLine("Custom size PDF generated successfully");
}
}Gotenberg requires string-based parameters ("8.5", "11", "0.5") added to multipart form data—no type safety, no IntelliSense, easy to mistype. IronPDF provides strongly-typed properties with PdfPaperSize enums and numeric margin values. Note that IronPDF margins are in millimeters (50mm ≈ 2 inches), while Gotenberg uses inches.
Critical Migration Notes
Unit Conversions
The most important conversion in this Gotenberg migration is the margin units:
// Gotenberg: margins in inches
content.Add(new StringContent("0.5"), "marginTop"); // 0.5 inches
content.Add(new StringContent("1"), "marginBottom"); // 1 inch
// IronPDF: margins in millimeters
renderer.RenderingOptions.MarginTop = 12.7; // 0.5 inches × 25.4 = 12.7mm
renderer.RenderingOptions.MarginBottom = 25.4; // 1 inch × 25.4 = 25.4mm// Gotenberg: margins in inches
content.Add(new StringContent("0.5"), "marginTop"); // 0.5 inches
content.Add(new StringContent("1"), "marginBottom"); // 1 inch
// IronPDF: margins in millimeters
renderer.RenderingOptions.MarginTop = 12.7; // 0.5 inches × 25.4 = 12.7mm
renderer.RenderingOptions.MarginBottom = 25.4; // 1 inch × 25.4 = 25.4mmConversion formula: millimeters = inches × 25.4
Synchronous vs Asynchronous
Gotenberg requires async operations because of HTTP communication:
// Gotenberg: Forced async due to network calls
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
// IronPDF: Synchronous in-process execution
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
// IronPDF: Async wrapper if needed
var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(html));// Gotenberg: Forced async due to network calls
var response = await client.PostAsync(gotenbergUrl, content);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
// IronPDF: Synchronous in-process execution
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
// IronPDF: Async wrapper if needed
var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(html));Error Handling
// Gotenberg: HTTP error handling
try
{
var response = await client.PostAsync(gotenbergUrl, content);
response.EnsureSuccessStatusCode(); // What if 500? 503? Timeout?
}
catch (HttpRequestException ex) { /* Network error */ }
catch (TaskCanceledException ex) { /* Timeout */ }
// IronPDF: Standard .NET exceptions
try
{
var pdf = renderer.RenderHtmlAsPdf(html);
}
catch (Exception ex)
{
Console.WriteLine($"PDF generation failed: {ex.Message}");
}// Gotenberg: HTTP error handling
try
{
var response = await client.PostAsync(gotenbergUrl, content);
response.EnsureSuccessStatusCode(); // What if 500? 503? Timeout?
}
catch (HttpRequestException ex) { /* Network error */ }
catch (TaskCanceledException ex) { /* Timeout */ }
// IronPDF: Standard .NET exceptions
try
{
var pdf = renderer.RenderHtmlAsPdf(html);
}
catch (Exception ex)
{
Console.WriteLine($"PDF generation failed: {ex.Message}");
}Infrastructure Removal
After migration, remove Gotenberg from your infrastructure:
# REMOVE from docker-compose.yml:
# services:
# gotenberg:
# image: gotenberg/gotenberg:8
# ports:
# - "3000:3000"
# deploy:
# resources:
# limits:
# memory: 2G# REMOVE from docker-compose.yml:
# services:
# gotenberg:
# image: gotenberg/gotenberg:8
# ports:
# - "3000:3000"
# deploy:
# resources:
# limits:
# memory: 2GPerformance Considerations
Latency Comparison
| Operation | Gotenberg (Warm) | Gotenberg (Cold Start) | IronPDF (First Render) | IronPDF (Subsequent) |
|---|---|---|---|---|
| Simple HTML | 150-300ms | 2-5 seconds | 1-2 seconds | 50-150ms |
| Complex HTML | 500-1500ms | 3-7 seconds | 1.5-3 seconds | 200-800ms |
| URL Render | 1-5 seconds | 3-10 seconds | 1-5 seconds | 500ms-3s |
Infrastructure Cost Elimination
| Resource | Gotenberg | IronPDF |
|---|---|---|
| Containers required | 1-N (scaling) | 0 |
| Memory per container | 512MB-2GB | N/A |
| Network overhead per request | 10-100ms | 0ms |
| Health check endpoints | Required | Not needed |
| Load balancer | Often needed | Not needed |
Troubleshooting
Issue 1: HttpClient Patterns Not Needed
Problem: Code still using HttpClient and MultipartFormDataContent.
Solution: Replace entirely with ChromePdfRenderer:
// Remove all of this:
// using var client = new HttpClient();
// using var content = new MultipartFormDataContent();
// content.Add(new StringContent(html), "files", "index.html");
// var response = await client.PostAsync(url, content);
// Replace with:
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);// Remove all of this:
// using var client = new HttpClient();
// using var content = new MultipartFormDataContent();
// content.Add(new StringContent(html), "files", "index.html");
// var response = await client.PostAsync(url, content);
// Replace with:
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);Issue 2: Margin Units Wrong
Problem: PDFs have incorrect margins after migration.
Solution: Convert inches to millimeters:
// Gotenberg used inches: "0.5"
// IronPDF uses millimeters: 0.5 × 25.4 = 12.7
renderer.RenderingOptions.MarginTop = 12.7;// Gotenberg used inches: "0.5"
// IronPDF uses millimeters: 0.5 × 25.4 = 12.7
renderer.RenderingOptions.MarginTop = 12.7;Issue 3: Container URL References
Problem: Code contains http://gotenberg:3000 or similar URLs.
Solution: Remove all container URL references—IronPDF runs in-process:
// Remove:
// private readonly string _gotenbergUrl = "http://gotenberg:3000";
// IronPDF needs no URL - it's in-process
var renderer = new ChromePdfRenderer();// Remove:
// private readonly string _gotenbergUrl = "http://gotenberg:3000";
// IronPDF needs no URL - it's in-process
var renderer = new ChromePdfRenderer();Migration Checklist
Pre-Migration
- Inventory all Gotenberg HTTP calls in codebase
- Document current Gotenberg configuration (timeouts, margins, paper sizes)
- Identify all Docker/Kubernetes Gotenberg configuration
- Obtain IronPDF license key
- Plan infrastructure decommissioning
Code Migration
- Install IronPdf NuGet package:
dotnet add package IronPdf - Remove Gotenberg client packages
- Replace all HTTP calls to Gotenberg with IronPDF method calls
- Convert margin units from inches to millimeters
- Update error handling (HTTP errors → .NET exceptions)
- Add license key initialization at startup
Infrastructure Migration
- Remove Gotenberg from Docker Compose / Kubernetes
- Update CI/CD pipelines (remove Gotenberg image pulls)
- Remove Gotenberg health checks
- Remove Gotenberg URL from configuration
Testing
- Test HTML to PDF conversion
- Test URL to PDF conversion
- Verify margin and sizing accuracy
- Performance test under load
- Test first-render warmup time
Post-Migration
- Remove Gotenberg container deployments
- Archive Gotenberg configuration files
- Update documentation
- Monitor application memory usage
- Verify no orphaned network connections






