Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.idea
.pytest_cache
.ruff_cache
__pycache__
*.py[cod]
*.iml
.env
.DS_Store
build
dist
out
target
graphify-out
91 changes: 67 additions & 24 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,42 +1,85 @@
# Use a base image with CUDA support and the desired Python version
FROM python:3.12-slim-bookworm
FROM python:3.12-slim-bookworm AS builder

ARG CPU_ONLY=true
ARG PRELOAD_MODELS=true
ARG POETRY_VERSION=1.8.4
ARG VIRTUAL_ENV=/opt/venv

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1 \
PIP_ROOT_USER_ACTION=ignore \
VIRTUAL_ENV="${VIRTUAL_ENV}" \
POETRY_VIRTUALENVS_CREATE=false \
HF_HOME=/models/huggingface \
TORCH_HOME=/models/torch \
OMP_NUM_THREADS=4 \
PATH="${VIRTUAL_ENV}/bin:${PATH}"

ARG CPU_ONLY=false
WORKDIR /app

RUN apt-get update \
&& apt-get install -y redis-server libgl1 libglib2.0-0 curl wget git procps \
&& apt-get clean
&& apt-get install -y --no-install-recommends libgl1 libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*

# Install Poetry and configure it
RUN pip install poetry \
&& poetry config virtualenvs.create false
RUN python -m venv "${VIRTUAL_ENV}" \
&& /usr/local/bin/pip install "poetry==${POETRY_VERSION}"

COPY pyproject.toml poetry.lock ./

# Install dependencies before torch
RUN poetry install --no-interaction --no-root
# Install locked application dependencies into the runtime virtual environment.
RUN poetry install --only main --no-interaction --no-root

# Install PyTorch separately based on CPU_ONLY flag
RUN if [ "$CPU_ONLY" = "true" ]; then \
pip install --no-cache-dir torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu; \
# Replace the PyTorch wheel from the lock file with the target CPU/GPU variant.
RUN packages="$("${VIRTUAL_ENV}/bin/python" -c 'import importlib.metadata as m; names = {"torch", "torchvision", "torchaudio", "triton"}; print(" ".join(d.metadata["Name"] for d in m.distributions() if d.metadata["Name"] in names or d.metadata["Name"].startswith("nvidia-")))')"; \
if [ -n "$packages" ]; then "${VIRTUAL_ENV}/bin/pip" uninstall --yes $packages; fi; \
if [ "$CPU_ONLY" = "true" ]; then \
"${VIRTUAL_ENV}/bin/pip" install --no-cache-dir torch torchvision --index-url https://download.pytorch.org/whl/cpu; \
else \
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121; \
"${VIRTUAL_ENV}/bin/pip" install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121; \
fi

RUN mkdir -p /models/huggingface /models/torch

RUN if [ "$PRELOAD_MODELS" = "true" ]; then \
python -c 'from docling.pipeline.standard_pdf_pipeline import StandardPdfPipeline; StandardPdfPipeline.download_models_hf(force=True);'; \
fi

RUN if [ "$PRELOAD_MODELS" = "true" ]; then \
CPU_ONLY="$CPU_ONLY" python -c 'import os, easyocr; gpu = os.environ.get("CPU_ONLY", "true").lower() != "true"; easyocr.Reader(["fr", "de", "es", "en", "it", "pt"], gpu=gpu); print("EasyOCR models downloaded successfully")'; \
fi

ENV HF_HOME=/tmp/ \
TORCH_HOME=/tmp/ \
OMP_NUM_THREADS=4
FROM python:3.12-slim-bookworm AS runtime

ARG VIRTUAL_ENV=/opt/venv
ARG APP_UID=10001
ARG APP_GID=10001

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
HF_HOME=/models/huggingface \
TORCH_HOME=/models/torch \
OMP_NUM_THREADS=4 \
PATH="${VIRTUAL_ENV}/bin:${PATH}"

RUN python -c 'from docling.pipeline.standard_pdf_pipeline import StandardPdfPipeline; artifacts_path = StandardPdfPipeline.download_models_hf(force=True);'
WORKDIR /app

RUN apt-get update \
&& apt-get install -y --no-install-recommends libgl1 libglib2.0-0 libgomp1 tini \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid "${APP_GID}" docling \
&& useradd --system --uid "${APP_UID}" --gid docling --home-dir /app --shell /usr/sbin/nologin docling \
&& mkdir -p /models/huggingface /models/torch \
&& chown -R docling:docling /app /models

# Pre-download EasyOCR models in compatible groups
RUN python -c 'import easyocr; \
reader = easyocr.Reader(["fr", "de", "es", "en", "it", "pt"], gpu=True); \
print("EasyOCR models downloaded successfully")'
COPY --from=builder "${VIRTUAL_ENV}" "${VIRTUAL_ENV}"
COPY --from=builder --chown=docling:docling /models /models
COPY --chown=docling:docling . .

COPY . .
USER docling

EXPOSE 8080

CMD ["poetry", "run", "uvicorn", "--port", "8080", "--host", "0.0.0.0", "main:app"]
ENTRYPOINT ["tini", "--"]
CMD ["uvicorn", "--port", "8080", "--host", "0.0.0.0", "main:app"]
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,15 @@ curl -X POST "http://localhost:8080/documents/convert" \

## Environment Setup (Running in Docker)

For detailed Docker instructions, including CPU mode, GPU mode, service URLs, verification commands, configuration, and troubleshooting, see [docs/docker.md](docs/docker.md).

1. Clone the repository:
```bash
git clone https://github.com/drmingler/docling-api.git
cd docling-api
```

2. Create a `.env` file:
2. Optional: create a `.env` file if you need to override the default Docker Redis connection:
```bash
REDIS_HOST=redis://redis:6379/0
ENV=production
Expand All @@ -147,13 +149,13 @@ ENV=production
### CPU Mode
To start the service using CPU-only processing, use the following command. You can adjust the number of Celery workers by specifying the --scale option. In this example, 1 worker will be created:
```bash
docker-compose -f docker-compose.cpu.yml up --build --scale celery_worker=1
docker compose up --build --scale celery_worker=1
```

### GPU Mode (Recommend for production)
For production, it is recommended to enable GPU acceleration, as it significantly improves performance. Use the command below to start the service with GPU support. You can also scale the number of Celery workers using the --scale option; here, 3 workers will be launched:
```bash
docker-compose -f docker-compose.gpu.yml up --build --scale celery_worker=3
docker compose -f docker-compose.gpu.yml up --build --scale celery_worker=3
```

## Service Components
Expand Down
74 changes: 47 additions & 27 deletions docker-compose.cpu.yml
Original file line number Diff line number Diff line change
@@ -1,70 +1,90 @@
version: "3.8"

services:
celery_worker:
build:
context: .
args:
CPU_ONLY: "true"
image: converter-cpu-image
command: poetry run celery -A worker.celery_config worker --pool=solo -n worker_primary --loglevel=info
image: docling-api:cpu
command: celery -A worker.celery_config worker --pool=solo -n worker_primary --loglevel=info
user: "10001:10001"
security_opt:
- no-new-privileges:true
volumes:
- .:/app
- model_cache:/tmp
- model_cache:/models
environment:
- REDIS_HOST=${REDIS_HOST}
- ENV=production
REDIS_HOST: redis://redis:6379/0
ENV: production
MALLOC_ARENA_MAX: "2"
OMP_NUM_THREADS: "2"
PYTHONMALLOC: malloc
restart: on-failure
depends_on:
- redis

app:
container_name: marker-api-cpu
container_name: docling-api-cpu
build:
context: .
args:
CPU_ONLY: "true"
image: converter-cpu-image
command: poetry run uvicorn --port 8080 --host 0.0.0.0 main:app
image: docling-api:cpu
command: uvicorn --port 8080 --host 0.0.0.0 main:app
user: "10001:10001"
security_opt:
- no-new-privileges:true
environment:
- REDIS_HOST=${REDIS_HOST}
- ENV=production
- MALLOC_ARENA_MAX=2
- OMP_NUM_THREADS=2
- PYTHONMALLOC=malloc
REDIS_HOST: redis://redis:6379/0
ENV: production
MALLOC_ARENA_MAX: "2"
OMP_NUM_THREADS: "2"
PYTHONMALLOC: malloc
ports:
- "8080:8080"
volumes:
- .:/app
- model_cache:/tmp
- model_cache:/models
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/docs', timeout=5).read()",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
restart: on-failure
depends_on:
- redis

redis:
container_name: redis
container_name: docling-api-redis
image: redis:7.2.4-alpine
ports:
- "6379:6379"
restart: on-failure

flower:
container_name: flower_cpu
container_name: docling-api-flower-cpu
build:
context: .
args:
CPU_ONLY: "true"
image: converter-cpu-image
command: poetry run celery -A worker.celery_config flower --port=5555
image: docling-api:cpu
command: celery -A worker.celery_config flower --port=5555
user: "10001:10001"
security_opt:
- no-new-privileges:true
ports:
- "5556:5555"
volumes:
- .:/app
- model_cache:/tmp
- model_cache:/models
environment:
- REDIS_HOST=${REDIS_HOST}
- ENV=production
REDIS_HOST: redis://redis:6379/0
ENV: production
restart: on-failure
depends_on:
- app
- redis
- celery_worker

Expand Down
71 changes: 49 additions & 22 deletions docker-compose.gpu.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
version: "3.8"

services:
celery_worker:
build:
context: .
args:
CPU_ONLY: "false"
image: converter-gpu-image
command: poetry run celery -A worker.celery_config worker --pool=solo -n worker_primary --loglevel=info
image: docling-api:gpu
command: celery -A worker.celery_config worker --pool=solo -n worker_primary --loglevel=info
user: "10001:10001"
security_opt:
- no-new-privileges:true
volumes:
- .:/app
- model_cache:/models
environment:
- REDIS_HOST=${REDIS_HOST}
- ENV=production
REDIS_HOST: redis://redis:6379/0
ENV: production
NVIDIA_VISIBLE_DEVICES: all
restart: on-failure
deploy:
resources:
reservations:
Expand All @@ -24,21 +27,37 @@ services:
- redis

app:
container_name: marker-api-gpu
container_name: docling-api-gpu
build:
context: .
args:
CPU_ONLY: "false"
image: converter-gpu-image
command: poetry run uvicorn --port 8080 --host 0.0.0.0 main:app
image: docling-api:gpu
command: uvicorn --port 8080 --host 0.0.0.0 main:app
user: "10001:10001"
security_opt:
- no-new-privileges:true
environment:
- REDIS_HOST=${REDIS_HOST}
- ENV=production
- NVIDIA_VISIBLE_DEVICES=all
REDIS_HOST: redis://redis:6379/0
ENV: production
NVIDIA_VISIBLE_DEVICES: all
ports:
- "8080:8080"
volumes:
- .:/app
- model_cache:/models
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/docs', timeout=5).read()",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
restart: on-failure
deploy:
resources:
reservations:
Expand All @@ -51,28 +70,33 @@ services:
- celery_worker

redis:
container_name: redis
container_name: docling-api-redis
image: redis:7.2.4-alpine
ports:
- "6379:6379"
restart: on-failure

flower:
container_name: flower_gpu
container_name: docling-api-flower-gpu
build:
context: .
args:
CPU_ONLY: "false"
image: converter-gpu-image
command: poetry run celery -A worker.celery_config flower --port=5555
image: docling-api:gpu
command: celery -A worker.celery_config flower --port=5555
user: "10001:10001"
security_opt:
- no-new-privileges:true
ports:
- "5556:5555"
volumes:
- .:/app
- model_cache:/models
environment:
- REDIS_HOST=${REDIS_HOST}
- ENV=production
REDIS_HOST: redis://redis:6379/0
ENV: production
NVIDIA_VISIBLE_DEVICES: all
restart: on-failure
depends_on:
- app
- redis
- celery_worker
deploy:
Expand All @@ -82,3 +106,6 @@ services:
- driver: nvidia
count: 1
capabilities: [gpu]

volumes:
model_cache:
Loading