Docker Containerization
Two Dockerfiles, no docker-compose. Production builds run in ACR (see the azure-container-deployment skill); local builds are useful for smoke-testing only.
Main app: Dockerfile
Multi-stage build:
- Frontend stage —
node:18-slim.npm cifromfrontend/package*.json, thennpm run build→frontend/dist. - Runtime stage —
python:3.11-slim. Installs Tesseract (tesseract-ocr-engonly),poppler-utils, build toolchain. Installs Python deps. Copiesmodels/(the Helsinki fine-tuned weights).COPY . .for app code.COPY --from=frontend-builder /app/frontend/dist ./frontend/dist.
Final command:
gunicorn --bind 0.0.0.0:8000 --workers 2 --timeout 300 \
--access-logfile - --error-logfile - app:app
The container runs as the default user (root in python:3.11-slim). There is no HEALTHCHECK and no dedicated non-root user. If you want either, add them — current state is "no".
The build context is the entire repo (COPY . .), filtered by .dockerignore. Anything not excluded ships into the image — be deliberate when adding large fixtures.
Ollama sidecar: Dockerfile.ollama
python:3.11-slim (not the official ollama/ollama base) plus the upstream installer:
RUN curl -fsSL https://ollama.com/install.sh | sh
Build arg PREPULL_LLM=false (default). When true, the image bakes in llama3.2:3b (Dockerfile.ollama) — much larger image, faster cold start.
Entrypoint is ollama-entrypoint.sh which runs ollama serve and ensures the chuukese-translator custom model exists.
System dependencies (and why they're there)
| Package | Reason |
|---|---|
tesseract-ocr + tesseract-ocr-eng |
OCR (src/ocr/ocr_processor.py) |
poppler-utils |
pdf2image rasterization for PDF OCR |
gcc, g++, python3-dev |
Building wheels for pymongo/numpy extensions |
Tesseract data packs other than English are not installed — Chuukese is handled by post-processing at the OCR layer.
Local build & run (for smoke testing)
# Build (slow on M-series due to amd64 cross — prefer ACR)
docker build -t chuuk-dictionary-app .
docker build -t chuuk-ollama -f Dockerfile.ollama .
# Run
docker run --rm -p 8000:8000 \
-e COSMOS_MONGO_CONNECTION_STRING="$COSMOS_MONGO_CONNECTION_STRING" \
-e FLASK_SECRET_KEY=dev-only \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
chuuk-dictionary-app
docker run --rm -p 11434:11434 chuuk-ollama
For full-stack local dev, use dev-start.sh (Vite + Flask side-by-side, no containers).
.dockerignore
Excludes .venv/, node_modules/, tests/, .git/, uploads/, output/, training_data/, plus the usual cache/byte-code patterns. Note: models/ is not excluded — they're intentionally baked in.
Common modifications
Adding a system package
Add the line to the apt-get install -y \\ block in Dockerfile. Combine with existing packages to keep one layer.
Adding a Python package
Add it to requirements.txt. Don't pip install in a new RUN line — that fragments the image.
Adding a frontend dependency
cd frontend && npm install <pkg>. The build stage will pick it up via npm ci.
Adding a Tesseract language
RUN apt-get update && apt-get install -y tesseract-ocr-<lang> && rm -rf /var/lib/apt/lists/*
Place it inside the existing system-deps block.
Pitfalls
node:18-slimis the current frontend base. Frontend builds fine on it; bumping tonode:20/node:22is safe but verify Vite 7 + React 19 still build cleanly.- The frontend stage's
WORKDIR /app/frontendand the runtime stage'sWORKDIR /appmean theCOPY --from=frontend-builder /app/frontend/dist ./frontend/distpath is stage-local to the source side — don't shorten it. pip install --root-user-action=ignoreis set because the runtime user is root; that flag is not "ignored" — it suppresses pip's warning. If you switch to a non-root user, drop the flag.--workers 2 --timeout 300is tuned for OCR/translate latency. Reducing the timeout will cut off long publication-processing SSE streams (see publication-ocr-processing-workflow).- The
CMDis plain JSON-array exec form. Don't switch to shell form unless you really want PID 1 semantics changed.