LibreOffice in Docker for Document Processing

Running LibreOffice in headless mode inside isolated Docker containers allows microservice architectures to perform robust, automated document conversions (such as DOCX to PDF) at scale. This guide outlines how to build a lightweight LibreOffice container, execute conversions programmatically via a microservice API, isolate user profiles for concurrency, and apply security and resource limits to ensure system stability.

1. Building a Lightweight Docker Image

To minimize image size while ensuring all necessary rendering libraries and fonts are present, use a lightweight base image like Alpine Linux or Debian Slim.

Create a Dockerfile:

FROM debian:bullseye-slim

# Install LibreOffice headless and essential fonts
RUN apt-get update && apt-get install -y --no-install-recommends \
    libreoffice-writer \
    libreoffice-calc \
    libreoffice-impress \
    fonts-liberation \
    fonts-dejavu \
    python3 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

# Create a non-root user for security
RUN useradd -m -u 1000 appuser

WORKDIR /app
USER appuser

Installing font packages (fonts-liberation, fonts-dejavu) is critical to prevent text displacement and visual artifacts during document conversions.

2. Setting Up an HTTP Microservice Wrapper

LibreOffice operates via CLI. To use it in a microservice architecture, wrap the CLI commands in a lightweight web server (such as Python’s FastAPI) that accepts file uploads and returns the converted output.

Example app.py:

from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import FileResponse
import subprocess
import tempfile
import os
import shutil

app = FastAPI()

@app.post("/convert")
async def convert_document(file: UploadFile = File(...)):
    with tempfile.TemporaryDirectory() as temp_dir:
        input_path = os.path.join(temp_dir, file.filename)
        with open(input_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)

        # Isolated user profile directory for thread-safety
        user_profile_dir = os.path.join(temp_dir, "profile")

        cmd = [
            "libreoffice",
            "--headless",
            "--invisible",
            "--nodefault",
            "--nofirststartwizard",
            "--nolockcheck",
            "--nologo",
            "--norestore",
            f"-env:UserInstallation=file://{user_profile_dir}",
            "--convert-to", "pdf",
            "--outdir", temp_dir,
            input_path
        ]

        try:
            result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30)
            if result.returncode != 0:
                raise HTTPException(status_code=500, detail="Conversion failed.")
        except subprocess.TimeoutExpired:
            raise HTTPException(status_code=408, detail="Conversion timed out.")

        base_name = os.path.splitext(file.filename)[0]
        output_path = os.path.join(temp_dir, f"{base_name}.pdf")

        if not os.path.exists(output_path):
            raise HTTPException(status_code=500, detail="Output file not generated.")

        return FileResponse(output_path, media_type="application/pdf", filename=f"{base_name}.pdf")

3. Key Conversion Flags for Concurrency

By default, LibreOffice limits operations to a single instance tied to a shared user profile. To allow multiple containers or threads to convert documents concurrently without locking conflicts, include the following flags:

4. Resource and Security Hardening

LibreOffice parses complex, untrusted file formats. Strict Docker isolation mitigates the risk of exploits, memory leaks, and CPU exhaustion.

Run the container with resource limits and restricted privileges:

docker run -d \
  --name libreoffice-converter \
  --p 8000:8000 \
  --memory="1g" \
  --cpus="1.5" \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=512m \
  --tmpfs /app:rw,noexec,nosuid,size=512m \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  libreoffice-converter-image

5. Lifecycle Management and Scaling

Because LibreOffice can accumulate unreleased memory over prolonged execution periods, implement the following lifecycle practices: