docker run 한 줄이면 브라우저에서 바로 쓰는 PDF 작업실이 열립니다. 데스크톱 앱·브라우저 UI·셀프호스팅 서버(+REST API) 세 가지 모습으로 동작하고, UI는 40개 언어를 지원합니다. 최근 대규모 리라이트로 Java 25 + Spring Boot 백엔드 + React 19 + Tauri 데스크톱 + 파이썬 AI 엔진이라는 멀티-런타임 구조가 됐습니다. (Stirling-Tools/Stirling-PDF · Java/TypeScript/Python · 오픈코어 · PDFBox 3 기반)"인터넷의 무료 PDF 사이트(합치기·압축·변환…)를 통째로 내 서버에 가둔 자급자족 PDF 작업실"입니다. 우리가 흔히 쓰는 ilovepdf·smallpdf 같은 온라인 PDF 도구는 편하지만, 내 문서를 남의 서버에 올려야 합니다. 계약서·주민등록증·의료기록이라면 꺼림칙하죠. Stirling-PDF는 그 모든 기능을 한 덩어리로 묶어 docker run -p 8080:8080 한 줄로 띄우고, 파일이 내 네트워크 밖으로 나가지 않게 만든 도구입니다.
이름의 'Stirling'은 특별한 뜻이라기보다 프로젝트 브랜드명입니다. 핵심은 self-hosted(셀프호스팅) + PDF + 50+ 도구라는 조합입니다. 라이선스는 오픈코어(open-core) — 기본 기능은 무료 오픈소스이고, SSO·감사로그 같은 기업용 기능은 별도 라이선스로 제공합니다. GitHub에서 "1위 PDF 애플리케이션"을 표방하며, Docker Pull 수가 수천만 건에 이르는 대표적 셀프호스팅 프로젝트입니다.
복사가게에 가면 제본기·스캐너(OCR)·재단기·도장(서명)·번역 코너가 다 있습니다. 편하지만, 내 서류를 점원에게 맡겨야 하죠. 온라인 PDF 사이트가 딱 이 복사가게입니다.
Stirling-PDF는 그 가게의 기계 50종을 내 방에 들여놓은 것과 같습니다. 제본(합치기)·재단(분할)·도장(서명)·번역(변환)·글자 인식(OCR)을 전부 내 방에서 합니다. 점원(외부 서버)이 없으니 서류를 남에게 보여줄 일이 없습니다. 대신 기계 설치·전기세(서버 운영)는 내 몫입니다.
2026년 셀프호스팅·프라이버시 흐름의 한가운데에 있는 프로젝트라 꾸준히 트렌딩에 오릅니다. 특히 최근 대규모 리라이트(레거시 Thymeleaf 서버 렌더링 → React 19 SPA + Tauri 데스크톱 앱)가 진행되면서, "오래된 자바 웹앱"이 "현대적 풀스택 + 데스크톱 + AI"로 탈바꿈하는 과정 자체가 학습 소재로서 흥미롭습니다. 단순 기능 나열이 아니라, 한 프로젝트가 어떻게 멀티-런타임·오픈코어로 성장하는가를 보여주는 사례입니다.
| 항목 | 온라인 PDF 사이트 | 유료 데스크톱(Acrobat류) | Stirling-PDF |
|---|---|---|---|
| 데이터 위치 | 외부 서버 업로드 | 내 PC | 내 PC·서버(밖으로 안 나감) |
| 비용 | 무료~구독 | 고가 구독 | 오픈소스 무료(코어) |
| 접근 방식 | 브라우저 | 설치형 앱 | 브라우저·데스크톱·API 모두 |
| 자동화 | 제한적 | 제한적 | REST API·노코드 파이프라인 |
| 기능 수 | 사이트마다 일부 | 풍부 | 50+ 도구 한 곳에 |
| 기업 기능 | 플랜별 | 플랜별 | SSO·감사(엔터프라이즈) |
모든 처리가 내가 띄운 컨테이너 안에서 일어납니다. 외부 API 호출 없이 PDFBox·LibreOffice·Tesseract 같은 로컬 엔진으로 변환·OCR을 수행합니다. 민감한 법무·의료·금융 문서를 다루는 조직이 "클라우드 PDF 도구 금지" 정책 아래에서도 쓸 수 있는 이유입니다.
같은 코어가 ① Tauri 데스크톱 앱 ② 브라우저 UI ③ 셀프호스팅 서버(+private API) 세 형태로 배포됩니다. 개인은 데스크톱 앱으로, 팀은 사내 서버로, 개발자는 REST API로 수백만 건의 PDF를 자동 처리할 수 있습니다. "한 코드베이스, 여러 배포 타깃"의 실전 예시입니다.
UI에서 "분할 → OCR → 압축 → 워터마크" 같은 작업을 코드 없이 엮어 자동화하는 파이프라인 기능이 있습니다. 여기에 최근 파이썬 기반 AI Document Engine이 붙어, PDF를 마크다운으로 변환하거나 문서 내 모순을 찾는 등 LLM 기반 작업까지 확장 중입니다.
온라인 PDF 도구가 "편하지만 남의 가게"라면, Stirling-PDF는 "내가 차린 PDF 종합공장"이다. 기능 수·자동화·세 가지 배포 형태가 강점이고, 진짜 배울 건 "PDF라는 복잡한 포맷을 어떻게 50개 도구로 다루며, 그걸 어떻게 멀티-런타임으로 패키징하는가"다.
Stirling-PDF는 단일 언어 프로젝트가 아닙니다. Java(핵심 PDF 처리) + TypeScript/React(UI·데스크톱) + Python(AI 엔진)이라는 세 런타임이 한 저장소(monorepo) 안에 공존합니다. 빌드는 Gradle 멀티모듈로 묶고, 배포는 Docker 이미지 여러 변형으로 나눕니다. "오픈코어"라서 모듈이 core / common / proprietary / saas 네 갈래로 나뉘는 점도 특징입니다.
| 항목 | 내용 |
|---|---|
| 언어 | Java 25 (sourceCompatibility VERSION_25) — 매우 최신 LTS 채택 |
| 프레임워크 | Spring Boot (actuator·jackson·webmvc), 단 내장 Tomcat은 제외하고 다른 서버 사용 |
| PDF 엔진 | Apache PDFBox 3.0.7 — PDF 읽기·쓰기·렌더의 핵심 라이브러리 |
| 보조 라이브러리 | Lombok(보일러플레이트 제거), Logback(로깅), java-security-toolkit(보안), CommonMark(마크다운) |
| 빌드 | Gradle 9.x 멀티모듈 + foojay-resolver(JDK 자동 다운로드) + Depot 원격 빌드캐시 |
| 모듈 구성 | :stirling-pdf(app/core) · :common · :proprietary · (옵션):saas |
| 항목 | 내용 |
|---|---|
| UI 프레임워크 | React 19 + React Router 7, TypeScript |
| PDF 렌더링 | EmbedPDF 플러그인 다수(annotation·redaction·search·thumbnail·zoom 등) + pdfjs-dist + @cantoo/pdf-lib |
| UI 컴포넌트 | Mantine 8 + MUI 9 + Emotion + Tailwind(PostCSS) |
| 데스크톱 | Tauri 2 — 웹 UI를 가벼운 네이티브 앱으로 패키징(Electron 대안) |
| 국제화 | i18next + 브라우저 언어감지 — 40개 언어 |
| 드래그앤드롭·기타 | dnd-kit, pragmatic-drag-and-drop, signature_pad(서명), qrcode.react, recharts/d3(차트) |
| 항목 | 내용 |
|---|---|
| 런타임 | Python 3.13, 패키지 관리 uv + uv.lock, 빌드 hatchling |
| 웹 프레임워크 | FastAPI + uvicorn (5001 포트) |
| AI 프레임워크 | pydantic-ai (+ voyageai 임베딩), pydantic-settings |
| 벡터 저장 | pgvector(Postgres) 또는 sqlite-vec — RAG용 임베딩 저장 |
| 에이전트 | pdf_create · contradiction(모순 탐지) · pdf_comment · ledger 등 다중 에이전트 |
| 관측성 | OpenTelemetry + PostHog |
| 항목 | 내용 |
|---|---|
| 변환 엔진 | LibreOffice(unoserver 3.6) — Office↔PDF 변환을 별도 컨테이너로 분리 가능 |
| OCR | Tesseract tessdata 볼륨 마운트(언어팩) |
| Docker 이미지 | ultra-lite(최소)·기본·fat(전기능) 3종 + embedded(프론트 내장)·frontend(nginx) 분리형 |
| 빌드 도구 | Task(taskfile.dev)를 통합 커맨드 러너로 사용 — task install |
| 배포 옵션 | Docker · Kubernetes · 데스크톱 설치본 · devcontainer |
Office 변환에 쓰는 LibreOffice는 무겁고 느리다. Stirling-PDF는 이를 메인 앱에 욱여넣는 대신 unoserver 컨테이너로 분리해 원격 호출(remote-uno mode)할 수 있게 했다. 덕분에 "변환 안 쓰는 사람"은 ultra-lite 이미지로 가볍게, "전부 쓰는 사람"은 fat으로 무겁게 — 필요에 맞춰 고를 수 있다. 무거운 의존성을 옵션화하는 좋은 사례다.
unoserver는 그 변환 요청을 받는 데몬이다. 무겁기 때문에 Stirling-PDF는 이를 선택적 별도 컨테이너로 둔다.core), 기업용 부가기능은 별도 라이선스(proprietary·saas)로 파는 사업 모델. Stirling-PDF는 빌드 시 STIRLING_FLAVOR(core/proprietary/saas) 값으로 어떤 모듈을 포함할지 정한다. 하나의 코드베이스에서 무료판·유료판을 갈라 빌드하는 실전 패턴이다.큰 그림은 "얇은 프론트엔드 + 두꺼운 PDF 처리 백엔드 + 선택적 외부 엔진"입니다. 사용자가 브라우저나 데스크톱 앱에서 PDF를 올리면, React UI가 그 요청을 Spring Boot REST API로 보내고, 백엔드의 컨트롤러가 PDFBox(혹은 LibreOffice·Tesseract)를 호출해 결과 PDF를 돌려줍니다. AI 작업은 별도 파이썬 엔진으로 위임됩니다.
백엔드의 controller/api/ 아래에는 기능 하나당 컨트롤러 하나가 거의 1:1로 매핑됩니다. MergeController(합치기)·CropController(자르기)·EditTableOfContentsController(목차편집)·ConvertOfficeController(오피스변환)처럼요. 전체 60여 개 컨트롤러가 각자 REST 엔드포인트 하나씩을 맡습니다. 새 PDF 도구를 추가하려면 컨트롤러 하나 + 모델(model/api/) 하나를 더하면 되는, 매우 추가-친화적 구조입니다(저장소의 ADDING_TOOLS.md가 이 절차를 문서화).
# 기능별로 정리된 컨트롤러 패키지(일부) controller/api/converters/ # 변환 (HTML/이미지/마크다운/오피스 ↔ PDF) controller/api/security/ # 암호화·권한·서명 controller/api/misc/ # OCR·압축·메타데이터 등 controller/api/pipeline/ # 노코드 작업 연쇄 controller/api/form/ # PDF 입력 폼 처리
주방에 "한 가지 요리만 하는 전문 코너"가 50개 있는 푸드코트와 같다. 파스타 코너(합치기), 초밥 코너(서명), 디저트 코너(OCR)… 각 코너(컨트롤러)는 자기 요리만 책임진다. 새 메뉴를 늘리고 싶으면 코너 하나를 더 차리면 되고, 다른 코너는 건드릴 필요가 없다.
settings.gradle이 STIRLING_FLAVOR 환경변수를 읽어 어떤 모듈을 빌드에 포함할지 결정합니다. core면 오픈소스 기능만, proprietary면 기업 기능 추가, saas면 그 위에 멀티테넌시까지. 한 저장소에서 무료판·유료판·클라우드판을 갈라 빌드하는, 오픈코어 회사의 전형적 모노레포 전략입니다.
Dockerfile이 빌드를 여러 단계로 쪼갭니다(멀티스테이지). 자바·프론트를 빌드한 뒤(gradle build -PbuildWithFrontend=true), java -Djarmode=tools ... extract --layers로 JAR을 레이어 단위로 분해해 런타임 이미지에 얹습니다. 의존성처럼 잘 안 바뀌는 레이어는 캐시되고, 자주 바뀌는 앱 코드만 새로 빌드돼 이미지 빌드·배포가 빨라집니다. JDK 25에서 바뀐 JDK_JAVA_OPTIONS 사용 같은 최신 자바 운영 디테일도 엿볼 수 있습니다.
PDF 처리(자바)와 AI 처리(파이썬)는 잘 맞물리지 않는 생태계입니다. Stirling-PDF는 둘을 억지로 합치는 대신, AI를 engine/의 독립 FastAPI 서비스로 떼어 5001 포트로 통신합니다. agents/ 아래에 pdf_create(PDF 생성)·contradiction(문서 모순 탐지)·pdf_comment·ledger 같은 다중 에이전트를 두고, api/routes/가 각 작업을 라우팅합니다. "언어가 다른 두 세계를 HTTP로 잇는다"는 마이크로서비스 발상입니다.
기본 docker run은 가볍지만, Office 변환(LibreOffice)·다국어 OCR(tessdata)·AI 엔진(파이썬+임베딩 DB)을 모두 켜면 메모리·디스크 요구량이 확 늘어난다. 처음엔 ultra-lite나 기본 이미지로 시작하고, 필요한 기능만 켜는 게 정석이다. "PDF 합치기만 할 건데 fat 이미지를 받는" 식의 과설치를 피하자.
세 가지가 핵심입니다. 첫째, 언어별로 폴더가 통째로 갈린다(app/=자바, frontend/=TS, engine/=파이썬). 모노레포지만 런타임 경계가 선명합니다. 둘째, 자바 백엔드 안은 다시 모듈(core/common/proprietary/saas)로 갈려 오픈코어 경계를 코드 구조로 강제합니다. 셋째, controller/api/가 기능별로 잘게 쪼개져 있어 "어떤 기능이 어디 있는지" 폴더 이름만 봐도 찾을 수 있습니다. 거대한 프로젝트를 길 잃지 않게 정리하는 법의 교과서입니다.
루트에 ADDING_TOOLS.md(새 도구 추가법)·DeveloperGuide.md(22KB)·FILE_SHARING.md·SHARED_SIGNING.md(24KB)·DATABASE.md 등 기여자용 문서가 빽빽하다. AGENTS.md는 AI 에이전트가 코드를 이해하도록 쓴 안내서다. "기여를 받으려면 진입장벽을 문서로 낮춰야 한다"는 대형 오픈소스의 운영 철학이 드러난다.
controller/api/의 각 컨트롤러는 PDFBox API 사용 예제의 보물창고입니다. 페이지 객체·콘텐츠 스트림·폰트·메타데이터를 직접 만지는 코드를 읽으면 PDF 내부 구조가 보입니다. 실습: PDFBox로 PDF 두 개를 합치고, 워터마크 텍스트를 얹는 50줄짜리 자바 프로그램 작성하기.
settings.gradle의 환경변수 기반 모듈 선택은 오픈코어 빌드의 정석입니다. 실습: :core와 :pro 두 모듈을 만들고, -PenablePro=true일 때만 :pro를 포함하도록 settings.gradle을 작성해 보기.
docker/embedded/Dockerfile의 멀티스테이지 + jarmode=tools extract --layers는 빌드 캐시를 극대화하는 실전 기법입니다. 실습: 아무 Spring Boot 앱을 레이어드 JAR로 분해해 Docker 이미지로 만들고, 코드 한 줄만 바꿨을 때 재빌드 시간이 얼마나 줄어드는지 측정하기.
frontend/package.json의 EmbedPDF 플러그인 묶음은 "브라우저에서 PDF를 보고 편집하는 법"의 모범 구성입니다. 실습: EmbedPDF나 pdfjs로 PDF를 띄우고, 페이지 썸네일·검색·줌 기능을 붙인 미니 뷰어 만들기.
같은 React 코드가 Tauri로 네이티브 앱이 됩니다. 실습: 간단한 React 앱을 Tauri 2로 감싸 데스크톱 실행파일을 빌드하고, Electron 버전과 용량·메모리를 비교하기.
engine/의 FastAPI + pydantic-ai + 벡터DB(pgvector/sqlite-vec) 구성은 "기존 앱에 AI를 곁붙이는 법"입니다. 실습: FastAPI로 "PDF 텍스트를 받아 마크다운으로 변환"하는 엔드포인트를 만들고, pydantic-ai 에이전트로 요약까지 붙여 보기.
| 항목 | 요구사항 |
|---|---|
| 실행 방식 | Docker(권장) · Kubernetes · 데스크톱 설치본 — 셋 중 택1 |
| 기본 메모리 | 경량 사용은 1~2GB로 충분. OCR·Office변환·AI 켜면 4GB+ 권장 |
| 포트 | 메인 앱 8080, (옵션) AI 엔진 5001, unoserver 2003 |
| 볼륨 | /usr/share/tessdata(OCR 언어팩)·/configs·/logs 마운트 |
| OCR 언어팩 | Tesseract tessdata — 필요한 언어만 받아 디스크 절약 |
| Office 변환 | LibreOffice 포함 이미지(fat) 또는 별도 unoserver 컨테이너 |
| 개발 환경 | JDK 25 + Node 20 + Python 3.13(uv) + Task(taskfile.dev) |
# 도커만 있으면 끝. 8080 으로 브라우저 접속 docker run -p 8080:8080 docker.stirlingpdf.com/stirlingtools/stirling-pdf # → http://localhost:8080
# docker/compose/docker-compose.yml 발췌 services: stirling-pdf: ports: ["8080:8080"] volumes: - ./data:/usr/share/tessdata # OCR 언어팩 - ./config:/configs # 설정 영속화 - ./logs:/logs environment: DISABLE_ADDITIONAL_FEATURES: "true" # 코어 기능만 SECURITY_ENABLELOGIN: "false" # 로그인 없이 SYSTEM_MAXFILESIZE: "100" # 업로드 100MB 제한
task install # 통합 러너로 의존성 세팅 gradle clean build -PbuildWithFrontend=true # 자바+프론트 함께 빌드 STIRLING_FLAVOR=core gradle bootRun # 오픈소스 코어만 실행
docker run -p 8080:8080 ...로 띄운 뒤, PDF 두 개를 합치고 분할하고 압축까지 해 본다. 어떤 작업이 빠르고 어떤 작업이 느린지(특히 OCR·Office변환) 체감하고, 각 기능이 백엔드의 어떤 컨트롤러에 대응할지 폴더에서 찾아 매칭해 보기.
UI 대신 /api/v1/... 엔드포인트를 curl로 직접 호출해 PDF를 합쳐 본다. API 문서(Scalar)를 보고 멀티파트 업로드 형식을 익히고, 같은 작업을 셸 스크립트로 반복 자동화하기.
UI의 Pipeline 기능으로 "분할 → OCR → 압축 → 워터마크"를 한 번에 도는 파이프라인을 정의한다. 그 파이프라인 JSON 정의를 내보내, 같은 처리를 API로도 트리거할 수 있는지 확인하기.
ultra-lite와 fat 두 이미지를 모두 받아, 이미지 용량·기동 시간·메모리 사용량을 측정한다. Office 변환을 시켜 보고 ultra-lite에서 어떤 기능이 빠지는지 확인하기. "기능 옵션화"의 효과를 수치로 체험.
ADDING_TOOLS.md를 따라, 예를 들어 "모든 페이지에 페이지 번호를 찍는" 새 도구를 컨트롤러 + 모델 + 프론트 버튼으로 추가한다. PDFBox로 페이지 하단에 텍스트를 그리고, 빌드해서 UI에 노출시키기.
engine/를 로컬에서 띄우고, agents/ 구조를 본떠 "PDF 표를 CSV로 추출"하는 에이전트를 추가한다. FastAPI 라우트로 노출하고, pydantic-ai로 모델 호출 + 결과 검증(validators)까지 연결하기.
| 주차 | 학습 주제 | 실천 목표 |
|---|---|---|
| 1주차 | PDF 포맷 기초 + PDFBox | PDF 내부 구조(객체·스트림·폰트) 이해, PDFBox로 합치기/워터마크 구현 |
| 2주차 | Spring Boot REST API | 멀티파트 업로드를 받아 파일을 변환해 돌려주는 컨트롤러 작성 |
| 3주차 | Gradle 멀티모듈·플레이버 | 환경변수로 모듈 포함을 토글하는 오픈코어식 빌드 구성 |
| 4주차 | Docker 멀티스테이지·레이어드 JAR | 빌드 캐시를 극대화하는 Spring Boot 이미지 작성·측정 |
| 5주차 | React 19 + PDF.js/EmbedPDF | 브라우저 PDF 뷰어(썸네일·검색·줌) 구현 |
| 6주차 | Tauri 데스크톱 패키징 | 웹앱을 네이티브 앱으로, Electron과 비교 분석 |
| 7주차 | OCR·LibreOffice headless 변환 | Tesseract·unoserver로 스캔본·오피스 파일 처리 파이프라인 |
| 8주차 | FastAPI + pydantic-ai + 벡터DB | 문서 RAG 마이크로서비스를 기존 앱에 HTTP로 연결 |
STIRLING_FLAVOR(core/proprietary/saas)로 빌드에 포함할 모듈을 고른다.jarmode=tools extract --layers) Docker 캐시를 극대화하는 기법. 코드만 바뀌면 그 레이어만 새로 빌드된다.| 링크 | 설명 |
|---|---|
| GitHub 저장소 | 소스 전체. app/core/·frontend/·engine/·docker/가 핵심 |
| 공식 문서 | 설치·기능·배포(Kubernetes·데스크톱) 가이드 |
| ADDING_TOOLS.md | 새 PDF 도구(컨트롤러+모델) 추가 절차 |
| DeveloperGuide.md | 개발 환경·빌드·기여 가이드(가장 두꺼움) |
| TrendShift 페이지 | 트렌딩 추이(일간 8위, 2026-06-09) |
| Apache PDFBox | PDF 처리 핵심 라이브러리(3.x) |
| Tauri 2 | 데스크톱 앱 패키징 프레임워크 |
| EmbedPDF | 브라우저 PDF 뷰어 플러그인 |
| pydantic-ai | AI 엔진이 쓰는 LLM 에이전트 프레임워크 |
| Task | 빌드·개발·테스트 통합 커맨드 러너 |