Artificial Intelligence (AI) Azure

Docling Serve on Ubuntu 24.04 on Azure User Guide

| Product: Docling Serve on Ubuntu 24.04 LTS on Azure

Overview

Docling Serve is the HTTP API service around Docling, an open source toolkit that converts documents into clean, structured content. Point it at a PDF, DOCX, PPTX, HTML or image file and it returns well structured Markdown or JSON in which headings, paragraphs, lists and tables survive the conversion, using machine learning models that understand page layout and reconstruct table structure. Applications POST a document and receive content ready to index, chunk for retrieval augmented generation, feed to a language model, or store. Docling and Docling Serve are Linux Foundation AI & Data projects, originally created at IBM Research, and are released under the MIT licence.

The cloudimg image installs Docling Serve into a Python virtual environment at /opt/docling, runs it under uvicorn as an unprivileged docling system account bound to loopback, fronts it with a single nginx gateway on TCP 80 that enforces HTTP Basic authentication on every path, and generates a unique API password on the first boot of every VM. The layout, table structure and OCR models are baked into the image and the service runs in offline mode, so both text based and scanned documents convert without the VM ever reaching out to the internet. Backed by 24/7 cloudimg support.

What is included:

  • docling-serve 1.27.0 and the docling conversion engine (docling-slim 2.114.0), both MIT licensed
  • PyTorch 2.13 (CPU build) — no GPU is required anywhere
  • Baked ML models, all licences verified against the artifacts at build time: document layout analysis (docling-layout-heron, Apache-2.0), table structure / TableFormer (docling-models, CDLA-Permissive-2.0), and OCR (RapidOCR PP-OCR, Apache-2.0)
  • nginx as an authenticating reverse proxy, the only network reachable surface
  • A built in UI playground at /ui and interactive API docs at /docs
  • A per VM API password generated on first boot into a root only file
  • An unauthenticated /healthz endpoint for load balancer probes

Docling Serve running on the cloudimg Azure image with the docling-serve and nginx services active, the uvicorn backend bound to loopback only on port 5001, and the unauthenticated health probe returning ok

Key facts

Item Value
Platform Ubuntu 24.04 LTS
Default SSH user azureuser
Install root /opt/docling
Baked models /opt/docling/models
Credentials file /root/docling-credentials.txt
API user admin
Backend (loopback) 127.0.0.1:5001
Public API TCP 80 via nginx
UI playground /ui
API docs (Swagger UI) /swagger
Recommended size Standard_B2ms (2 vCPU / 8 GiB)

Prerequisites

  • An Azure subscription and the Azure CLI or access to the Azure Portal.
  • An SSH key pair for connecting to the VM.
  • A network security group that allows inbound TCP 22 (SSH) and TCP 80 (the API) from the addresses you will connect from. Restrict both to your own IP ranges.
  • The recommended size is Standard_B2ms (2 vCPU / 8 GiB). The models load to roughly 2.7 GiB resident; the appliance runs on a 4 GiB VM for evaluation but B2ms gives headroom for larger documents and concurrent requests. No GPU is needed.

Step 1 — Deploy from the Azure Marketplace

  1. In the Azure Portal, choose Create a resource and search the Marketplace for Docling Serve on Ubuntu 24.04 by cloudimg.
  2. Select the plan and click Create.
  3. On Basics, choose your subscription and resource group, a region, and the VM size (Standard_B2ms recommended). Set the authentication type to SSH public key, the username to azureuser, and paste your public key.
  4. On Networking, attach or create a network security group that allows inbound TCP 22 and TCP 80 from your own IP ranges only.
  5. Review and create. When deployment finishes, note the VM's public IP address.

Step 2 — Deploy from the Azure CLI

az vm create \
  --resource-group my-rg \
  --name docling-serve \
  --image cloudimg:docling-serve-ubuntu-24-04:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Open the API port to your own address (replace 203.0.113.10/32 with your IP):

az vm open-port --resource-group my-rg --name docling-serve --port 80 --priority 900

Step 3 — Connect to your VM

ssh azureuser@<vm-public-ip>

The login banner shows where to find the credentials file, the model provenance and this guide.

Step 4 — Confirm the service is running

systemctl is-active docling-serve.service nginx.service

Expected output:

active
active

The uvicorn backend listens on loopback only and nginx is the single authenticated front end. Confirm the bind and the unauthenticated health probe:

ss -tln | grep -E ':5001|:80 '
curl -s http://127.0.0.1/healthz

Expected output:

LISTEN 0      511          0.0.0.0:80        0.0.0.0:*
LISTEN 0      2048       127.0.0.1:5001      0.0.0.0:*
ok

The backend on 127.0.0.1:5001 is not reachable from outside the VM; every request arrives through nginx on port 80.

The docling-serve and nginx services reported active, the docling-serve backend bound to 127.0.0.1:5001 while only nginx listens on port 80, and the credentials file present with root only 0600 permissions

Step 5 — Retrieve your API password

Every VM generates its own password on first boot. There is no default login and no shared secret between deployments.

sudo cat /root/docling-credentials.txt

The file is readable only by root and contains DOCLING_URL, DOCLING_USERNAME and DOCLING_PASSWORD. Store the password in your secret manager and treat it as you would any API key. In the commands below, replace <DOCLING_PASSWORD> with this value.

Step 6 — Confirm authentication is enforced

Every API path requires the credential. An unauthenticated conversion request is refused:

curl -s -o /dev/null -w 'no credential -> HTTP %{http_code}\n' \
  -X POST http://127.0.0.1/v1/convert/file \
  -F 'files=@/opt/docling/samples/sample.pdf;type=application/pdf' \
  -F do_ocr=false -F to_formats=md

Expected output:

no credential -> HTTP 401

With the per VM password the same request succeeds:

curl -s -o /dev/null -w 'per VM password -> HTTP %{http_code}\n' \
  -u admin:<DOCLING_PASSWORD> \
  -X POST http://127.0.0.1/v1/convert/file \
  -F 'files=@/opt/docling/samples/sample.pdf;type=application/pdf' \
  -F do_ocr=false -F to_formats=md

Expected output:

per VM password -> HTTP 200

A wrong password is refused with 401 as well, so the credential is genuinely enforced rather than decorative.

Step 7 — Convert a document to Markdown

The image ships a small synthetic sample document at /opt/docling/samples/sample.pdf — a text layer PDF with a heading, a paragraph and a table. Convert it and extract the Markdown from the response. Passing do_ocr=false uses the fast, fully offline text extraction path together with the layout and table models.

curl -s -u admin:<DOCLING_PASSWORD> \
  -X POST http://127.0.0.1/v1/convert/file \
  -F 'files=@/opt/docling/samples/sample.pdf;type=application/pdf' \
  -F do_ocr=false -F to_formats=md \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["document"]["md_content"])'

Expected output:

## CLOUDIMG DOCLING SAMPLE INVOICE

This is a synthetic sample document authored by cloudimg to verify document conversion end to end. Verification marker: DOCLINGPROOF7Q2X.

## Line items

| Item          |   Quantity |   Unit Price |
|---------------|------------|--------------|
| Widget Alpha  |          3 |        12.00 |
| Sprocket Beta |          5 |         4.50 |
| Cog Gamma     |          2 |         9.75 |

The headings became Markdown headings and the table became a Markdown table — the layout model located the page elements and the TableFormer model reconstructed the table grid. To convert your own document, replace the file path. PDF, DOCX, PPTX, HTML, Markdown, AsciiDoc, CSV and common image formats are accepted.

A real conversion of the sample PDF returning structured Markdown that contains the document title, the verification marker and a reconstructed Markdown table, proving the baked layout and table models produced structure and not just plain text

The response is a JSON object with a document field. Besides md_content it also carries json_content, html_content, text_content and doctags_content, so you can ask for whichever representation suits your pipeline via to_formats (for example -F to_formats=json).

Step 8 — Run OCR on a scanned image (fully offline)

Scanned or image only documents have no text layer, so they need optical character recognition. The RapidOCR models are baked into the image, so OCR runs entirely offline — nothing is sent to any external service. The image ships a synthetic scanned sample at /opt/docling/samples/ocr_sample.png. Convert it with do_ocr=true:

curl -s -u admin:<DOCLING_PASSWORD> \
  -X POST http://127.0.0.1/v1/convert/file \
  -F 'files=@/opt/docling/samples/ocr_sample.png;type=image/png' \
  -F do_ocr=true -F force_ocr=true -F to_formats=md \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["document"]["md_content"])'

Expected output:

## OCRPROOF ZEBRA 4831

synthetic scanned image, no text layer

The recognised text is returned as Markdown. To convert your own scanned document, replace the file path. OCR is more CPU intensive than text extraction, so scanned documents take longer than text layer PDFs of the same length.

Step 9 — Use the built in UI playground and API docs

The image enables the Docling Serve playground at /ui and interactive API documentation (Swagger UI) at /swagger (a ReDoc reference is also available at /docs). Both are behind the same Basic authentication, so your browser prompts once for the admin credential. In a browser, go to http://<vm-public-ip>/ui and sign in.

The Docling Serve playground served behind the per VM credential, where a document can be uploaded and converted interactively in the browser

Upload a document and convert it to see the structured output rendered directly in the page.

The playground after converting the sample document, showing the reconstructed Markdown with the heading, the verification marker and the table rendered in the browser

The /swagger page is the interactive OpenAPI reference for the stable v1 API, where every endpoint and parameter is documented and can be tried directly in the browser.

The Docling Serve Swagger UI at /swagger listing the v1 conversion endpoints

The POST /v1/convert/file operation expanded in the API docs, showing the file upload and conversion options

Step 10 — Call the API from your application

Any HTTP client works. This Python example converts a local PDF and prints the Markdown:

import requests

with open("report.pdf", "rb") as fh:
    resp = requests.post(
        "http://<vm-public-ip>/v1/convert/file",
        auth=("admin", "<DOCLING_PASSWORD>"),
        files={"files": ("report.pdf", fh, "application/pdf")},
        data={"do_ocr": "false", "to_formats": "md"},
        timeout=600,
    )
resp.raise_for_status()
print(resp.json()["document"]["md_content"])

For very large documents or batch workloads, Docling Serve also exposes asynchronous conversion endpoints under /v1/convert/source/async with task polling; see /docs for the full contract.

The models and their licences

Document conversion is driven by machine learning models, and each model is a separately licensed artifact. This image bakes only models that permit commercial use and redistribution, and every licence was verified at build time against the shipped model card rather than a documentation badge. The exact models, their sizes and sha256 checksums are recorded on the image:

cat /opt/docling/MODEL-PROVENANCE.txt
Model Purpose Licence
docling-layout-heron Document layout analysis (RT-DETRv2) Apache-2.0
docling-models TableFormer Table structure reconstruction CDLA-Permissive-2.0
RapidOCR PP-OCR Optical character recognition Apache-2.0

None of the shipped models is research only or non commercial, and none is fine tuned from a restricted base. EasyOCR is deliberately not used because its text detector lineage is less clear cut; RapidOCR's PaddleOCR derived models are Apache-2.0 and are the OCR stack shipped. The service runs with HF_HUB_OFFLINE=1, so it always loads these baked models and never reaches out to download anything during a conversion.

Document handling and log hygiene

Documents you submit are the sensitive artifact for this product, so the image is configured to keep them off disk and out of the logs:

  • The nginx access log is disabled on every path, and nginx never logs request bodies.
  • The service runs at log level WARNING, so document filenames are not written to the system journal. Document content is never logged at any level.
  • The synchronous /v1/convert/file endpoint returns the result inline and does not persist the submitted document or its output on disk after the response.

You can confirm nothing you submit leaks into the logs by converting a document containing a known marker and then searching /var/log and the journal for it.

Server components

Component Role
docling-serve.service The uvicorn API server, bound to 127.0.0.1:5001, running as the docling user
nginx.service Authenticating reverse proxy on TCP 80, the only network reachable surface
docling-firstboot.service One shot unit that generates the per VM password on first boot and proves a real conversion round trip before marking the boot complete

Filesystem layout

Path Contents
/opt/docling/venv Python virtual environment with docling-serve and its dependencies
/opt/docling/models Baked layout, table structure and OCR models
/opt/docling/samples Synthetic sample documents used by the built in self test
/opt/docling/MODEL-PROVENANCE.txt The shipped models with sizes, sha256 and licences
/var/lib/docling Writable scratch and cache directory owned by the docling user
/root/docling-credentials.txt The per VM API password, root only
/etc/nginx/cloudimg-docling.htpasswd The nginx Basic auth password file

Managing the service

sudo systemctl restart docling-serve.service
sudo systemctl restart nginx.service
sudo journalctl -u docling-serve.service -n 50 --no-pager

Changing the API password

Set a new Basic auth password for the admin user and reload nginx:

sudo sh -c 'printf "admin:%s\n" "$(openssl passwd -apr1 NEW_PASSWORD)" > /etc/nginx/cloudimg-docling.htpasswd'
sudo systemctl reload nginx

Replace NEW_PASSWORD with your chosen password.

Enabling HTTPS

The image serves the API over HTTP on port 80. For production, terminate TLS in front of the service. The simplest approach is to point a DNS name at the VM and use Certbot with the nginx plugin:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d docling.example.com

Certbot obtains a certificate, rewrites the nginx site to listen on 443 and sets up automatic renewal. Restrict inbound access to TCP 443 (and 22) in your network security group once TLS is in place.

Troubleshooting

  • 401 Unauthorized — you did not supply the credential, or the password is wrong. Retrieve it with sudo cat /root/docling-credentials.txt and pass it as -u admin:PASSWORD.
  • The API does not respond — check systemctl is-active docling-serve.service nginx.service. The backend loads the models at startup, which takes a few seconds after a restart.
  • A conversion is slow — OCR (do_ocr=true) is CPU intensive; on a burstable size such as Standard_B2s sustained conversions can exhaust CPU credits. Use Standard_B2ms or larger for heavier workloads.
  • Read the logssudo journalctl -u docling-serve.service -n 100 --no-pager.

Security recommendations

  • Keep TCP 80 (and 22) restricted to your own IP ranges in the network security group.
  • Put the service behind TLS as shown above before using it in production.
  • Rotate the API password after first login and store it in a secret manager.
  • The uvicorn backend is bound to loopback; do not reconfigure it to bind 0.0.0.0, which would bypass the authenticating proxy.

On startup

On the first boot of every VM, docling-firstboot.service generates a unique 24 character API password, writes it to /root/docling-credentials.txt (root only), starts the service, and proves a real conversion round trip before marking the boot complete. No default credential is ever baked into the image. Subsequent boots reuse the generated password.

Support

Every cloudimg deployment is backed by 24/7 support. If you have questions about this image or need help with your deployment, contact the cloudimg support team.