# Deploy Python apps on Vercel using Docker 

**Author:** Anshuman Bhardwaj

---

Python applications often need more than the packages from PyPI. An OCR service may need the Tesseract executable and its language data. A media app depends on FFmpeg. A document processor reaches for Poppler or LibreOffice. A local AI pipeline may need model weights and native numerical libraries.

Those dependencies are part of the application, even when they are not listed in `requirements.txt`. A container image defines the Python runtime, operating-system packages, model assets, and startup command together. Vercel builds that image, runs it as a Function, and deploys it alongside the rest of your application.

This guide covers building a document extractor with Python and Next.js, an app that pulls text out of PDFs and images. It also covers when to use Docker and how to deploy Dockerized apps to Vercel.

Deploy the template now, or keep reading to build it yourself.

## Quick start with an AI coding agent

### Vercel Plugin

The [Vercel Plugin](https://vercel.com/docs/agent-resources/vercel-plugin) turns your AI coding agent (e.g., OpenAI Codex, Claude Code, or Cursor) into a Vercel expert. It adds skills, slash commands, and current knowledge of the tools this template uses. The plugin is optional; it isn't required to use the template or to follow this guide.

`npx plugins add vercel/vercel-plugin`

## When to use Docker

Vercel's [native Python runtime](https://vercel.com/docs/functions/runtimes/python) supports deploying Python applications, including popular frameworks like FastAPI, Flask, and Django. It installs packages from `pyproject.toml`, `requirements.txt`, or a Pipfile, and can deploy with little or no platform configuration. Use that path when the application and its runtime dependencies can be installed entirely through the supported Python environment.

Use a container image when the runtime also needs one or more of the following:

- An operating-system executable such as Tesseract, FFmpeg, or Chromium.
  
- Shared libraries or language data that a Python wheel does not include.
  
- A framework or server that Vercel does not detect natively.
  
- A small CPU model whose runtime and versioned weights need to ship together.
  
- The same OCI image in local development and production.
  

Docker does not turn the deployment into a general-purpose virtual machine. Vercel builds the image, stores it in [Vercel Container Registry](https://vercel.com/docs/container-registry), and runs it as an autoscaling Vercel Function on Fluid compute. The service must serve HTTP, remain stateless, scale down when idle, and adhere to Function limits on size, memory, and duration. To learn more, see [container execution model](https://vercel.com/kb/guide/does-vercel-support-docker-deployments).

### Why this project needs a container

The `pytesseract` Python dependency is a wrapper around the Tesseract OCR engine. Installing it via `pip` does not install the `tesseract` executable. The executable must be installed separately and available on `PATH`, which is why the [pytesseract installation instructions](https://github.com/madmaze/pytesseract#installation) list `tesseract-ocr` as a Debian or Ubuntu prerequisite.

The project, therefore, has two dependency layers:

- `requirements.txt` installs FastAPI, Uvicorn, Pillow, `pypdf`, `python-multipart`, and `pytesseract`.
  
- The Dockerfile installs the `tesseract-ocr` operating-system package.
  

`pypdf` [is not OCR software](https://pypdf.readthedocs.io/en/stable/user/extract-text.html), so it can extract existing PDF text but cannot recognize words from a scanned page image. Scanned PDF support requires an additional rendering step before Tesseract can process each page.

## How to use Docker

Vercel looks for `Dockerfile.vercel` (or `Containerfile.vercel`) in the root directory. In this repository, the API service points to `apps/api/Dockerfile.vercel`:

`FROM python:3.12-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PORT=8000 RUN apt-get update && apt-get install -y --no-install-recommends \ tesseract-ocr \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app ./app EXPOSE 8000 CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port $PORT"]` Here’s a brief breakdown of the above Dockerfile: - `python:3.12-slim` supplies Python and Debian's package manager without the larger general-purpose image. The `ENV` values prevent bytecode files, make logs appear immediately, and provide a local port default.    - The first `RUN` instruction updates the package index and installs Tesseract in the same layer, skips recommended packages that the service does not need, and removes the temporary package index afterward. These choices follow [Docker's guidance for Debian-based images](https://docs.docker.com/build/building/best-practices/#apt-get).
  
- The dependency manifest is copied before the application source so Docker can reuse the package layer when only Python code changes. The final command binds Uvicorn to `0.0.0.0` and uses `$PORT`; Vercel routes to port `80` by default unless the `PORT` environment variable is configured.
  

## What Vercel Services does

Docker defines the API's execution environment. Vercel Services controls how the API and frontend are built, deployed to a single project, and routed.

The repository's `vercel.json` declares two services:

`{ "$schema": "https://openapi.vercel.sh/vercel.json", "services": { "web": { "root": "apps/web", "framework": "nextjs" }, "api": { "root": "apps/api", "runtime": "container", "entrypoint": "Dockerfile.vercel" } }, "rewrites": [{ "source": "/api/(.*)", "destination": { "service": "api" } }, { "source": "/(.*)", "destination": { "service": "web" } } ] }` The `web` service is built with Vercel's Next.js integration. The `api` service uses `apps/api` as its build context and `Dockerfile.vercel` as its container entrypoint. Each service has its own dependencies and build, but they belong to one deployment. The rewrites map public paths to services: - `/api/*` is handled by the container service.    - Every other path is handled by Next.js.    FastAPI includes `/api` in its route definitions because the original request path reaches the service. The browser calls `/api/extract` on the same origin as the page, so the project does not need a separate API hostname or routine CORS configuration. Container services run as Vercel Functions, not as permanent Docker hosts. Instances are stateless, may be reused while warm, scale with traffic, and can disappear when idle. Do not use local memory or disk as the system of record. ## How the application works The browser sends a `multipart/form-data` request to `/api/extract`. Vercel routes that path to FastAPI without exposing a second public origin. FastAPI reads the file into memory, enforces the application limit, and chooses an extractor from its content type or filename extension. PDF and image extraction take different paths: | Input | Processing path                    | What it can read                    | | ----- | ---------------------------------- | ----------------------------------- | | PDF   | `pypdf.PdfReader`                  | Text already embedded in PDF pages  | | Image | Pillow → `pytesseract` → Tesseract | Pixels containing recognizable text | `pypdf` does not turn scanned pages into text. A scanned PDF can, therefore, return an empty result even when the pages visibly contain words. Supporting scans requires rendering each page to an image before running OCR. These files define the architecture: - `apps/api/Dockerfile.vercel` defines the Python container image.    - `apps/api/app/main.py` exposes the FastAPI endpoints.    - `apps/api/app/extractor.py` selects PDF extraction or image OCR.    - `apps/web/components/upload-workbench.tsx` sends documents to the API.    - `vercel.json` declares and routes the two services.    ## Run locally and deploy Run the complete multi-service project with: `vercel dev -L` This starts the services from the repository root and applies the routing in `vercel.json`. To deploy, run `vercel deploy` from the project root or follow these steps: 1. Import the repository root as a Vercel project.     2. Confirm that the project's framework setting is set to **Services**. If not, update it.     3. Deploy the repository.     4. Upload a small PDF and a small image, then confirm both extraction paths.     ## Next steps You can make this application even more robust by adding storage, AI processing, asynchronous jobs, and production controls: - **Add Vercel Blob:** Upload larger documents directly from the browser, retain source files for retries, and store generated assets.    - **Add AI processing:** Use the [AI SDK](https://ai-sdk.dev/docs/introduction) after extraction for summaries, structured fields, classification, tagging, or document chat.
  
- **Persist jobs and results:** Store processing status, metadata, extracted text, and errors in a database while keeping file bytes in Blob.
  
- **Move long-running work to a queue:** Use durable [workflows](https://vercel.com/docs/workflows) for retries, page-level OCR, AI enrichment, cleanup, and progress tracking.
  
- **Support scanned PDFs:** Detect pages without embedded text, render them as images, and process them with Tesseract.
  
- **Add production controls:** Introduce authentication, authorization, rate limits, analytics, tracing, privacy-safe logs, and retention policies.
  

## Troubleshooting

### OCR fails with “tesseract is not installed”

**Cause:** `pytesseract` is installed, but the operating-system executable is absent or not on `PATH`.

**Fix:** build from `apps/api/Dockerfile.vercel`, confirm `tesseract-ocr` is in the `apt-get install` list, and run `tesseract --version` inside the image when debugging locally.

### OCR cannot load a language

**Cause:** The requested trained-data file is not installed or Tesseract cannot find its `tessdata` directory.

**Fix:** Install the matching `tesseract-ocr-<lang>` package, confirm the language appears in `tesseract --list-langs`, and set `TESSDATA_PREFIX` only when using a nonstandard data location.

### The container starts locally, but not on Vercel

**Cause:** The server may be bound to localhost, may ignore `$PORT`, or may exit during native dependency or model initialization.

**Fix:** Keep Uvicorn on `0.0.0.0:$PORT`, inspect the service logs, and move expensive or failure-prone initialization out of module import when appropriate.

### `/api/extract` returns the Next.js page or a 404

**Cause:** The repository was not deployed as a Services project, the rewrite is missing, or the FastAPI route does not include the `/api` prefix.

**Fix:** Deploy from the repository root, select the Services framework setting, keep the top-level rewrite, and verify `/api/health` before testing uploads.

### An image is small on disk but exhausts memory

**Cause:** Compressed file size does not represent decoded pixel memory. Very large dimensions or concurrent image copies can consume far more memory.

**Fix:** Keep Pillow's decompression-bomb protection, enforce pixel and page limits, resize only when safe, and measure peak memory with representative documents.

### Builds become slow after adding a model

**Cause:** Weights are being downloaded after the application code is copied, or an unversioned asset invalidates the image cache frequently.

**Fix:** Place stable model acquisition in a dependency layer, pin and checksum the asset, use a multi-stage build when needed, and reconsider remote inference if the model dominates the image.

## Related resources

- [Running Docker on Vercel](https://vercel.com/kb/guide/docker)
  
- [Does Vercel support Docker deployments?](https://vercel.com/kb/guide/does-vercel-support-docker-deployments)
  
- [The complete guide to Vercel Services](https://vercel.com/kb/guide/vercel-services)
  
- [Using the Python runtime with Vercel Functions](https://vercel.com/docs/functions/runtimes/python)
  
- [Tesseract documentation](https://tesseract-ocr.github.io/tessdoc/)

---

[View full KB sitemap](/kb/sitemap.md)
