Skip to content

Deploy Python apps on Vercel using Docker

Deploy a Dockerized Python application that depends on native software, OCR engines, or small local models, plus a Next.js frontend, on Vercel Services.

Anshuman BhardwajContent Engineer

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.

FastAPI + Next.js OCR template

Upload a PDF or image and extract its text in seconds using OCR.

Deploy Template

Copy link to headingQuick start with an AI coding agent

Agent Prompt

I want to build a document text extraction app using the FastAPI + Next.js Docker OCR template. Read the setup instructions at https://agent-resources.dev/ocr-fastapi-nextjs-docker-template.md and follow them. They cover deploying the template with Vercel Services, running it locally with the Vercel CLI and Docker, working with pypdf and Tesseract OCR, and understanding how PDF and image uploads are processed.

This guide covers the core implementation. The AI assistant prompt above covers all the details your coding agent needs, and you can see the full implementation in the template repository.

Copy link to headingVercel Plugin

The 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.

Terminal
npx plugins add vercel/vercel-plugin

Copy link to headingWhen to use Docker

Vercel's native Python runtime 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, 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.

Copy link to headingWhy 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 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, 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.

Copy link to headingHow 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:

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.
  • 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.

Copy link to headingWhat 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:

vercel.json
{
"$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.

Copy link to headingHow 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:

InputProcessing pathWhat it can read
PDFpypdf.PdfReaderText already embedded in PDF pages
ImagePillow → pytesseract → TesseractPixels 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.

Copy link to headingRun locally and deploy

Run the complete multi-service project with:

Terminal
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.

Copy link to headingNext 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 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 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.

Copy link to headingTroubleshooting

Copy link to headingOCR 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.

Copy link to headingOCR 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.

Copy link to headingThe 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.

Copy link to heading/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.

Copy link to headingAn 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.

Copy link to headingBuilds 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.

Copy link to headingRelated resources

Copy link to headingFAQ

What is the container image size limit?

The standard Function path allows a 250 MB uncompressed package, while the native Python runtime has a 500 MB standard allowance. The Large Functions public beta supports Node.js, Python, and container images up to 5 GB uncompressed on Fluid compute.

How long can the container process one request?

Container services inherit Vercel Function duration limits. On Fluid compute, the current documented default is 300 seconds. Hobby Functions can run for up to 300 seconds, while the standard maximum for Pro and Enterprise is 1800 seconds.

Can I use WebSockets with a Docker service?

Yes, with current limitations. WebSocket support for Vercel Functions is in public beta on Fluid compute, including container-image Functions. A connection stays pinned to one Function instance and is limited by that Function's maximum duration, so clients must reconnect.

Can I deploy the frontend and API together?

Yes. Vercel Services can build the Next.js and container services independently, route both through one public URL, and create a matching set of services for every preview deployment.

How large can an uploaded document be?

Vercel Functions accept request and response bodies up to 4.5 MB. This template sets its own limit to 4 MB in the browser and FastAPI service, leaving room for multipart form metadata. Files larger than the application limit receive a 413 response.

For larger documents, upload directly from the browser to Vercel Blob and send only the resulting object reference to the API. This keeps the file bytes out of the Function request body. Vercel recommends Blob client uploads for this pattern.

Related documentation

More Build Image guides