2025-12-02 17:15:29 +00:00
# Stirling-PDF Dockerfile - Full version with embedded frontend
# Single JAR contains both frontend and backend
# Stage 1: Build application with embedded frontend
FROM gradle:8.14-jdk21 AS build
# Install Node.js and npm for frontend build
RUN apt-get update && apt-get install -y \
curl \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& npm --version \
&& node --version \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Copy gradle files for dependency resolution
COPY build.gradle .
COPY settings.gradle .
COPY gradlew .
COPY gradle gradle/
COPY app/core/build.gradle core/.
COPY app/common/build.gradle common/.
COPY app/proprietary/build.gradle proprietary/.
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
# Set working directory
WORKDIR /app
# Copy entire project
COPY . .
# Build JAR with embedded frontend (includes security features controlled at runtime)
RUN DISABLE_ADDITIONAL_FEATURES = false \
STIRLING_PDF_DESKTOP_UI = false \
./gradlew clean build -PbuildWithFrontend= true -x spotlessApply -x spotlessCheck -x test -x sonarqube
2025-12-21 10:40:32 +00:00
# Stage 2: Runtime image based on Debian stable-slim
# Contains Java runtime + LibreOffice + Calibre + all PDF tools
FROM debian:stable-slim@sha256:7cb087f19bcc175b96fbe4c2aef42ed00733a659581a80f6ebccfd8fe3185a3d
2025-12-02 17:15:29 +00:00
2025-12-21 10:40:32 +00:00
SHELL [ "/bin/bash" , "-o" , "pipefail" , "-c" ]
ENV DEBIAN_FRONTEND = noninteractive
ENV TESS_BASE_PATH = /usr/share/tesseract-ocr/5/tessdata
# Install core runtime dependencies + tools required by Stirling-PDF features
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates tzdata tini bash fontconfig \
openjdk-21-jre-headless \
ffmpeg poppler-utils ocrmypdf imagemagick fontforge ghostscript \
libreoffice-nogui libreoffice-java-common \
python3 python3-venv python3-uno \
tesseract-ocr tesseract-ocr-eng tesseract-ocr-deu tesseract-ocr-fra \
tesseract-ocr-por tesseract-ocr-chi-sim \
libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libgdk-pixbuf-2.0-0 \
gosu unpaper \
# AWT headless support (required for some Java graphics operations)
libfreetype6 libfontconfig1 libx11-6 libxt6 libxext6 libxrender1 libxtst6 libxi6 \
libxinerama1 libxkbcommon0 libxkbfile1 libsm6 libice6 \
# Qt WebEngine dependencies for Calibre
libegl1 libopengl0 libgl1 libxdamage1 libxfixes3 libxshmfence1 libdrm2 libgbm1 \
libxkbcommon-x11-0 libxrandr2 libxcomposite1 libnss3 libx11-xcb1 \
libxcb-cursor0 libdbus-1-3 libglib2.0-0 \
# Virtual framebuffer (required for headless LibreOffice)
xvfb x11-utils coreutils \
# Temporary packages only needed for Calibre installer
xz-utils gpgv curl xdg-utils \
\
# Install Calibre from official installer script
&& curl -fsSL https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin \
\
# Clean up installer-only packages
&& apt-get purge -y xz-utils gpgv xdg-utils \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
# Make ebook-convert available in PATH
RUN ln -sf /opt/calibre/ebook-convert /usr/bin/ebook-convert \
&& /opt/calibre/ebook-convert --version
# ==============================================================================
# Create non-root user (stirlingpdfuser) with configurable UID/GID
# ==============================================================================
ARG PUID = 1000
ARG PGID = 1000
RUN set -eux; \
# Create group if it doesn't exist
if ! getent group stirlingpdfgroup >/dev/null 2>& 1; then \
if getent group " ${ PGID } " >/dev/null 2>& 1; then \
groupadd -o -g " ${ PGID } " stirlingpdfgroup; \
else \
groupadd -g " ${ PGID } " stirlingpdfgroup; \
fi ; \
fi ; \
# Create user if it doesn't exist, avoid UID conflicts
if ! id -u stirlingpdfuser >/dev/null 2>& 1; then \
if getent passwd | awk -F: -v id = " ${ PUID } " '$3==id{found=1} END{exit !found}' ; then \
echo "UID ${ PUID } already in use – creating stirlingpdfuser with automatic UID" ; \
useradd -m -g stirlingpdfgroup -d /home/stirlingpdfuser -s /bin/bash stirlingpdfuser; \
else \
useradd -m -u " ${ PUID } " -g stirlingpdfgroup -d /home/stirlingpdfuser -s /bin/bash stirlingpdfuser; \
fi ; \
fi
# Compatibility alias for older entrypoint scripts expecting su-exec
RUN ln -sf /usr/sbin/gosu /usr/local/bin/su-exec
# Copy application files from build stage
COPY --from= build --chown= stirlingpdfuser:stirlingpdfgroup /app/app/core/build/libs/*.jar /app.jar
COPY --from= build --chown= stirlingpdfuser:stirlingpdfgroup /app/build/libs/restart-helper.jar /restart-helper.jar
COPY scripts/ /scripts/
COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/truetype/
# Optional version tag (can be passed at build time)
2025-12-02 17:15:29 +00:00
ARG VERSION_TAG
2025-12-21 10:40:32 +00:00
# Metadata labels
2025-12-02 17:15:29 +00:00
LABEL org.opencontainers.image.title= "Stirling-PDF"
2025-12-21 10:40:32 +00:00
LABEL org.opencontainers.image.description= "Stirling-PDF with embedded frontend - Full version with Calibre, LibreOffice, Tesseract, OCRmyPDF, and more"
2025-12-02 17:15:29 +00:00
LABEL org.opencontainers.image.source= "https://github.com/Stirling-Tools/Stirling-PDF"
LABEL org.opencontainers.image.licenses= "MIT"
LABEL org.opencontainers.image.vendor= "Stirling-Tools"
LABEL org.opencontainers.image.url= "https://www.stirlingpdf.com"
LABEL org.opencontainers.image.documentation= "https://docs.stirlingpdf.com"
LABEL maintainer = "Stirling-Tools"
LABEL org.opencontainers.image.authors= "Stirling-Tools"
LABEL org.opencontainers.image.version= " ${ VERSION_TAG } "
LABEL org.opencontainers.image.keywords= "PDF, manipulation, API, Spring Boot, React"
2025-12-21 10:40:32 +00:00
# ==============================================================================
# Runtime environment variables
# ==============================================================================
2025-12-02 17:15:29 +00:00
ENV VERSION_TAG = $VERSION_TAG \
2025-12-21 10:40:32 +00:00
JAVA_BASE_OPTS = "-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70 -Djava.awt.headless=true" \
2025-12-02 17:15:29 +00:00
JAVA_CUSTOM_OPTS = "" \
HOME = /home/stirlingpdfuser \
2025-12-21 10:40:32 +00:00
PUID = ${ PUID } \
PGID = ${ PGID } \
2025-12-02 17:15:29 +00:00
UMASK = 022 \
UNO_PATH = /usr/lib/libreoffice/program \
STIRLING_TEMPFILES_DIRECTORY = /tmp/stirling-pdf \
TMPDIR = /tmp/stirling-pdf \
TEMP = /tmp/stirling-pdf \
TMP = /tmp/stirling-pdf
2025-12-21 10:40:32 +00:00
# ==============================================================================
# Python virtual environment for additional Python tools (WeasyPrint, OpenCV, etc.)
# ==============================================================================
RUN python3 -m venv /opt/venv --system-site-packages \
&& /opt/venv/bin/pip install --no-cache-dir weasyprint pdf2image opencv-python-headless \
&& /opt/venv/bin/python -c "import cv2; print('OpenCV version:', cv2.__version__)"
# Separate venv for unoserver (keeps it isolated)
RUN python3 -m venv /opt/unoserver-venv --system-site-packages \
&& /opt/unoserver-venv/bin/pip install --no-cache-dir unoserver
# Make unoserver tools available in main venv PATH
RUN ln -sf /opt/unoserver-venv/bin/unoconvert /opt/venv/bin/unoconvert \
&& ln -sf /opt/unoserver-venv/bin/unoserver /opt/venv/bin/unoserver
# Extend PATH to include both virtual environments
ENV PATH = "/opt/venv/bin:/opt/unoserver-venv/bin: ${ PATH } "
2025-12-02 17:15:29 +00:00
2025-12-21 10:40:32 +00:00
# ==============================================================================
# Final permissions, directories and font cache
# ==============================================================================
RUN set -eux; \
chmod +x /scripts/*; \
mkdir -p /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf; \
chown -R stirlingpdfuser:stirlingpdfgroup \
/home/stirlingpdfuser /configs /logs /customFiles /pipeline /tmp/stirling-pdf \
/app.jar /restart-helper.jar /usr/share/fonts/truetype /scripts; \
chmod -R 755 /tmp/stirling-pdf
# Rebuild font cache
RUN fc-cache -f -v
# Force Qt/WebEngine to run headlessly (required for Calibre in Docker)
ENV QT_QPA_PLATFORM = offscreen \
QTWEBENGINE_CHROMIUM_FLAGS = "--disable-gpu --disable-dev-shm-usage"
# Expose web UI port
2025-12-02 17:15:29 +00:00
EXPOSE 8080/tcp
2025-12-21 10:40:32 +00:00
STOPSIGNAL SIGTERM
# Use tini as init (handles signals and zombies correctly)
2025-12-02 17:15:29 +00:00
ENTRYPOINT [ "tini" , "--" , "/scripts/init.sh" ]
2025-12-21 10:40:32 +00:00
# CMD is empty – actual start command is defined in init.sh
CMD []