Docling Serve on Ubuntu 24.04 on Azure User Guide
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
/uiand interactive API docs at/docs - A per VM API password generated on first boot into a root only file
- An unauthenticated
/healthzendpoint for load balancer probes

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
- In the Azure Portal, choose Create a resource and search the Marketplace for Docling Serve on Ubuntu 24.04 by cloudimg.
- Select the plan and click Create.
- 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. - On Networking, attach or create a network security group that allows inbound TCP 22 and TCP 80 from your own IP ranges only.
- 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.

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.

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.

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

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.


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/fileendpoint 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 withsudo cat /root/docling-credentials.txtand 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 logs —
sudo 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.