Scope: this document explains how the project is containerized and deployed, based on the actual deployment files: Dockerfile, start.sh, requirements.txt, and .dockerignore. These files are present at the root level on HF Space so that the web-app is deployed.
The application is packaged as a Docker container and deployed as a dual-service runtime:
- FastAPI runs the classifier and GitHub automation endpoints on port
8000inside the container. - Flask runs the browser-facing app on port
7860by default, reading the Hugging Face SpacesPORTvariable when present.
The container is designed for a Hugging Face Spaces style runtime where the container exposes a single public port while internally launching both services.
💡 NOTE: The deployment uses two processes in one container, not a separate sidecar architecture. The
start.shscript launches both processes and exits if either one dies, which is a practical choice for Spaces-style hosting.
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && \
apt-get install -y --no-install-recommends git-lfs build-essential && \
rm -rf /var/lib/apt/lists/*
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
RUN chmod +x /app/start.sh
EXPOSE 7860
CMD ["/bin/bash", "/app/start.sh"]| Step | Meaning |
|---|---|
| Base image | python:3.11-slim for a small Python runtime |
| Working directory | /app |
| System packages | Installs git-lfs and build-essential |
| Source copy | Copies the full repository into the image |
| Python deps | Installs requirements.txt |
| Entrypoint | Runs /app/start.sh |
| Public port | Exposes 7860 |
The project stores model artifacts in the repository and the image prepares for Git LFS-backed assets if needed. That is a practical safeguard for Hugging Face-style deployment and larger model files.
⚠️ WARNING: The container copies the full repository into the image. Keep.dockerignoretight so notebooks, caches, and other unnecessary artifacts do not bloat the build context.
#!/usr/bin/env bash
set -e
FRONTEND_PORT="${PORT:-7860}"
BACKEND_PORT=8000
uvicorn api.backend:app --host 0.0.0.0 --port ${BACKEND_PORT} &
UVICORN_PID=$!
gunicorn app:app --bind 0.0.0.0:${FRONTEND_PORT} --workers 1 &
GUNICORN_PID=$!
wait -n ${UVICORN_PID} ${GUNICORN_PID}
EXIT_CODE=$?
kill ${UVICORN_PID} ${GUNICORN_PID} 2>/dev/null || true
exit ${EXIT_CODE}| Process | Command | Port | Role |
|---|---|---|---|
| Backend | uvicorn api.backend:app |
8000 |
FastAPI inference and GitHub automation |
| Frontend | gunicorn app:app |
7860 or PORT |
Flask UI host |
The two-process startup lets the app serve a browser UI while also keeping the model API alive for local proxying, Hugging Face traffic, and workflow automation.
graph TD
A[Container Start] --> B[start.sh]
B --> C[uvicorn api.backend:app :8000]
B --> D[gunicorn app:app :7860 or PORT]
C --> E[FastAPI endpoints ready]
D --> F[Flask UI ready]
E --> G[Browser on Hugging Face Spaces]
F --> G
| Package | Purpose |
|---|---|
fastapi |
API server for inference and automation |
flask |
Frontend app host |
uvicorn[standard] |
ASGI server for FastAPI |
httpx |
Async GitHub and API calls |
requests |
Flask-side proxy requests |
transformers |
DistilBERT model and tokenizer |
torch |
Neural network runtime |
numpy |
Numeric utilities for post-processing |
gunicorn |
Production WSGI server for Flask |
pydantic |
Request/response schema validation |
The dependency set is intentionally minimal and focused on serving, not training. That keeps the production container leaner than the notebook environment.
__pycache__
*.pyc
*.pyo
*.pyd
venv/
.venv/
env/
.git/
.gitignore
.ipynb_checkpoints/
*.pt
!pr_classifier.pt
*.bin
*.h5
*.ckpt
node_modules/
dist/
build/
.cache/| Ignore rule | Why it exists |
|---|---|
venv/, .venv/, env/ |
Prevent local virtual environments from being copied into the image |
.ipynb_checkpoints/ |
Remove notebook artifacts |
*.pt with !pr_classifier.pt |
Exclude large checkpoints except the production model artifact |
*.bin, *.h5, *.ckpt |
Exclude alternate model artifacts not used in deployment |
node_modules/, dist/, build/ |
Exclude frontend build outputs and dependency trees that are not needed here |
💡 NOTE: The exception
!pr_classifier.ptis important. It keeps the production model checkpoint in the build context while excluding other large model files.
The repository already exposes the local environment shape in src/.env.example, and the deployment runtime should extend that with any hosting-specific variables.
| Variable | Required | Used by | Purpose | Example |
|---|---|---|---|---|
FLASK_SECRET_KEY |
Recommended | Flask | Session and cookie signing | random secret |
API_BASE |
Required for local proxying | Flask | Base URL of the FastAPI service | http://localhost:8000 |
PORT |
Provided by host | Hugging Face Spaces / container runtime | External port for the Flask service | 7860 |
| GitHub token | User-provided secret | Frontend + FastAPI + n8n | Read/write GitHub access for private repos and label write-back | PAT or GitHub App token |
FLASK_SECRET_KEY=<your-secret-key>
API_BASE=http://localhost:8000| Secret | Where to store | Why |
|---|---|---|
| GitHub PAT / App token | Browser session storage for interactive UI; n8n credentials for automation; never in source control | Needed for private repo access and write-back |
| Flask secret key | Environment variable | Protects Flask session and signed cookies |
| n8n webhook secret | n8n credentials or secrets manager | Helps validate or protect the automation webhook |
⚠️ WARNING: Never commit GitHub tokens into the repository or into the Docker image. Treat them as runtime secrets only.
The repository README states that the app is deployed on Hugging Face Spaces, which is consistent with the Docker-based runtime and port 7860 exposure.
| Requirement | How this repository satisfies it |
|---|---|
| Single public port | EXPOSE 7860 and FRONTEND_PORT="${PORT:-7860}" |
| Deterministic startup | CMD ["/bin/bash", "/app/start.sh"] |
| Model availability | models/pr_classifier.pt included in the repo build context |
| API availability | FastAPI started inside the same container |
| UI availability | Flask started inside the same container |
The Flask UI is the public entry point, but it depends on the FastAPI process running inside the same container. That means a failure in either process makes the full app unhealthy.
sequenceDiagram
autonumber
participant HF as Hugging Face Spaces
participant C as Docker Container
participant F as Flask :7860
participant API as FastAPI :8000
participant GH as GitHub API
HF->>C: Start container from Dockerfile
C->>F: Launch gunicorn app:app
C->>API: Launch uvicorn api.backend:app
UI->>F: Open web app in browser
F->>API: Forward analysis request
API->>GH: Fetch or write GitHub data when needed
GH-->>API: JSON response
API-->>F: Prediction / action result
F-->>UI: Rendered UI update
The backend exposes a health endpoint:
| Field | Value |
|---|---|
| Endpoint | GET /health |
| Output | status, device, token_loaded |
| Use case | Container liveness check and quick service verification |
This is useful during deployment checks because the application has two internal processes and a model that must load correctly before the UI is fully usable.
The production deployment is a compact Dockerized two-process application:
- Python 3.11 slim base image,
- Flask UI on the public port,
- FastAPI on an internal port,
- a production model checkpoint copied into the image,
- and runtime secrets provided through environment variables or credential stores.
That setup is practical for Hugging Face Spaces and consistent with the repository’s actual startup scripts.