Spaces:
Build error
Build error
docs: add Docker/HF Spaces deployment plan and design spec
Browse files
docs/superpowers/plans/2026-06-14-docker-hf-spaces-deployment.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Docker / HF Spaces Deployment Plan
|
| 2 |
+
|
| 3 |
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
| 4 |
+
|
| 5 |
+
**Goal:** Containerize EuropaLex for zero-cost deployment on Hugging Face Spaces with CPU-only inference and baked-in model weights.
|
| 6 |
+
|
| 7 |
+
**Architecture:** A single-stage Dockerfile downloads all four AI models at build time (using a HF token secret), installs CPU-only dependencies, and launches Gradio on port 7860. The existing `_auto_download_models()` in `app.py` already skips download when `.gguf` files exist, so no code changes are needed β the runtime simply finds the pre-baked models and starts instantly.
|
| 8 |
+
|
| 9 |
+
**Tech Stack:** Docker (python:3.12-slim base), CPU-only PyTorch, llama-cpp-python (CPU backend), Hugging Face Hub CLI for model download.
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
### Task 1: Archive Current README
|
| 14 |
+
|
| 15 |
+
**Files:**
|
| 16 |
+
- Create: `docs/europalex-readme-old.md`
|
| 17 |
+
- Modify: `README.md` (replaced in Task 2)
|
| 18 |
+
|
| 19 |
+
- [ ] **Step 1: Copy current README to archive location**
|
| 20 |
+
|
| 21 |
+
Run:
|
| 22 |
+
```bash
|
| 23 |
+
cp README.md docs/europalex-readme-old.md
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
This preserves the current development-focused README before replacing it with the deployment-focused version.
|
| 27 |
+
|
| 28 |
+
- [ ] **Step 2: Verify the archive**
|
| 29 |
+
|
| 30 |
+
Run:
|
| 31 |
+
```bash
|
| 32 |
+
diff <(head -5 README.md) <(head -5 docs/europalex-readme-old.md)
|
| 33 |
+
# Should show no differences
|
| 34 |
+
ls -la docs/europalex-readme-old.md
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
- [ ] **Step 3: Commit**
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
git add docs/europalex-readme-old.md
|
| 41 |
+
git commit -m "docs: archive current README before Docker deployment rewrite"
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
### Task 2: Create New Deployment-Focused README
|
| 47 |
+
|
| 48 |
+
**Files:**
|
| 49 |
+
- Modify: `README.md`
|
| 50 |
+
|
| 51 |
+
Replace the entire file with:
|
| 52 |
+
|
| 53 |
+
```markdown
|
| 54 |
+
# EuropaLex β Docker / Hugging Face Spaces Deployment
|
| 55 |
+
|
| 56 |
+
AI-powered flashcard generator for European languages, deployed as a Docker container on [Hugging Face Spaces](https://huggingface.co/spaces). All four AI models are baked into the image at build time β the app starts instantly with zero wait.
|
| 57 |
+
|
| 58 |
+
> **CPU-only inference:** All inference runs on CPU. Expect slower performance (30+ seconds per sentence for translation, longer for TTS/images) in exchange for free hosting.
|
| 59 |
+
|
| 60 |
+
## Quick Start: Deploy to HF Spaces
|
| 61 |
+
|
| 62 |
+
1. **Create a new Space** on Hugging Face β select **Docker** as the framework
|
| 63 |
+
2. **Upload these files** from this repository: `Dockerfile`, `.dockerignore`, and all project files (`app.py`, `core/`, `frontend/`, `models/`, `export/`, `configs/`)
|
| 64 |
+
3. **Set secret** `HUGGING_FACE_HUB_TOKEN` in your Space settings (under **Secrets**) β this authenticates model download during the Docker build step
|
| 65 |
+
4. **Push to repo** β Hugging Face automatically builds the image and deploys
|
| 66 |
+
|
| 67 |
+
That's it. The first build takes ~5β15 minutes (downloading ~26 GB of models). Subsequent visits start instantly.
|
| 68 |
+
|
| 69 |
+
## What Gets Deployed
|
| 70 |
+
|
| 71 |
+
| Model | Role | Size | Runtime |
|
| 72 |
+
|---|---|---|---|
|
| 73 |
+
| MiniCPM5-1B Q8_0 | English text generation (Phase 1) | ~1.1 GB | llama-cpp-python |
|
| 74 |
+
| tiny-aya-water Q4_K_M | Translation (Phase 2) | ~2.1 GB | llama-cpp-python |
|
| 75 |
+
| OmniVoice Q8_0 | Text-to-speech audio | ~950 MB | omnivoice.cpp |
|
| 76 |
+
| FLUX.2-klein 4B Q4_K_M | Image generation | ~2.6 GB | diffusers |
|
| 77 |
+
|
| 78 |
+
**Total image size:** ~20β30 GB (models baked in at build time)
|
| 79 |
+
|
| 80 |
+
## How It Works
|
| 81 |
+
|
| 82 |
+
```
|
| 83 |
+
Docker build:
|
| 84 |
+
python:3.12-slim β pip install CPU deps β huggingface-cli login (build secret) β download all models β CMD ["python", "app.py"]
|
| 85 |
+
|
| 86 |
+
HF Spaces runtime:
|
| 87 |
+
Container starts β _auto_download_models() finds GGUF files β skips download β launches Gradio on :7860
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
The Dockerfile downloads all models during `docker build` using your HF token as a build secret. At runtime, the app detects pre-existing model files and skips download entirely β no authentication needed, no waiting.
|
| 91 |
+
|
| 92 |
+
## CPU Performance Expectations
|
| 93 |
+
|
| 94 |
+
| Operation | Expected Time |
|
| 95 |
+
|---|---|
|
| 96 |
+
| Phase 1: Generate 3 English sentences | ~30β60 seconds |
|
| 97 |
+
| Phase 2: Translate 3 sentences (tiny-aya) | ~1β3 minutes |
|
| 98 |
+
| Phase 2: TTS audio per sentence | ~5β15 seconds |
|
| 99 |
+
| Phase 2: Image generation per card | ~30β60+ seconds |
|
| 100 |
+
|
| 101 |
+
These are approximate and depend on the HF Spaces CPU tier. All features remain functional β just slower than a GPU setup.
|
| 102 |
+
|
| 103 |
+
## Local Docker Testing (Optional)
|
| 104 |
+
|
| 105 |
+
Build and test locally before deploying:
|
| 106 |
+
|
| 107 |
+
```bash
|
| 108 |
+
# Build the image (requires your HF token)
|
| 109 |
+
docker build \
|
| 110 |
+
--secret id=hf_token,env=HUGGING_FACE_HUB_TOKEN \
|
| 111 |
+
-t europalex .
|
| 112 |
+
|
| 113 |
+
# Run locally (port 7860)
|
| 114 |
+
docker run -p 7860:7860 europalex
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
The container serves Gradio on `http://localhost:7860`. Press Ctrl+C to stop.
|
| 118 |
+
|
| 119 |
+
## Architecture
|
| 120 |
+
|
| 121 |
+
EuropaLex uses a two-phase generation workflow:
|
| 122 |
+
|
| 123 |
+
1. **Phase 1** β Enter a scenario, select CEFR level (A0βC2), set batch size β MiniCPM5-1B generates English sentences
|
| 124 |
+
2. **Phase 2** β Select target language, toggle Audio/Images β tiny-aya translates, OmniVoice generates TTS, FLUX generates illustrations
|
| 125 |
+
|
| 126 |
+
Cards export as Anki `.apkg` files or zipped CSV folders with flat media files.
|
| 127 |
+
|
| 128 |
+
## Repository Structure
|
| 129 |
+
|
| 130 |
+
```
|
| 131 |
+
EuropaLex/
|
| 132 |
+
βββ Dockerfile # Single-stage build: deps + model download + Gradio launch
|
| 133 |
+
βββ .dockerignore # Exclude .venv, .git, models from build context
|
| 134 |
+
βββ README.md # This file β HF Spaces deployment guide
|
| 135 |
+
βββ app.py # Entry point β Gradio UI wiring, two-phase generation handlers
|
| 136 |
+
βββ pyproject.toml # Project config (uv)
|
| 137 |
+
βββ requirements.txt # pip install dependencies
|
| 138 |
+
βββ configs/settings.yaml # App settings, model paths, batch defaults
|
| 139 |
+
βββ core/ # Business logic
|
| 140 |
+
β βββ types.py # Pydantic models: CardData, CEFRLevel, TextResult, etc.
|
| 141 |
+
β βββ engine.py # MiniCPMTextEngine, LlamaCppTextEngine, EnginePool
|
| 142 |
+
β βββ audio_gen.py # TTSEngine (OmniVoice)
|
| 143 |
+
β βββ image_gen.py # ImageGenEngine (diffusers Flux2KleinPipeline)
|
| 144 |
+
β βββ text_gen.py # Sentence extraction + generation with retry loop
|
| 145 |
+
β βββ pipeline.py # Phase 2 translation orchestration
|
| 146 |
+
βββ frontend/ # Gradio 6 UI
|
| 147 |
+
β βββ ui/
|
| 148 |
+
β β βββ widgets.py # Styled toggle checkbox wrappers, Blocks builder
|
| 149 |
+
β β βββ cards.py # Card rendering, gallery layout, progress bar
|
| 150 |
+
β βββ css/custom.css # Plain-white theme, card styling, disabled states
|
| 151 |
+
βββ models/
|
| 152 |
+
β βββ download_models.py # HF Hub model downloader (runtime fallback)
|
| 153 |
+
βββ export/ # Export formats
|
| 154 |
+
β βββ apkg_export.py # Anki .apkg export via genanki
|
| 155 |
+
β βββ csv_export.py # CSV zip export with flat media files
|
| 156 |
+
β βββ anki_tunnel.py # MCP tunnel sync for live Anki import
|
| 157 |
+
βββ docs/ # Design specs and implementation plans
|
| 158 |
+
β βββ superpowers/ # Planning documents
|
| 159 |
+
βββ tests/ # Test suite (pytest)
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
## CEFR Levels
|
| 163 |
+
|
| 164 |
+
`[A0, A1, A2, B1, B2, C1, C2]`
|
| 165 |
+
|
| 166 |
+
- **A0:** Uses curated common words list (no text generation model needed)
|
| 167 |
+
- **A1βC2:** MiniCPM5-1B generates English sentences at the selected level in Phase 1; tiny-aya-water translates them in Phase 2
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
- [ ] **Step 2: Write the new README**
|
| 171 |
+
|
| 172 |
+
Write the content above to `README.md`, replacing all existing content.
|
| 173 |
+
|
| 174 |
+
- [ ] **Step 3: Verify**
|
| 175 |
+
|
| 176 |
+
Run:
|
| 177 |
+
```bash
|
| 178 |
+
head -5 README.md
|
| 179 |
+
# Should show "# EuropaLex β Docker / HF Spaces Deployment"
|
| 180 |
+
grep -c "Dockerfile\|HF Spaces\|HUGGING_FACE_HUB_TOKEN" README.md
|
| 181 |
+
# Should be > 0 for each term
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
- [ ] **Step 4: Commit**
|
| 185 |
+
|
| 186 |
+
```bash
|
| 187 |
+
git add README.md docs/europalex-readme-old.md
|
| 188 |
+
git commit -m "docs: rewrite README for Docker/HF Spaces deployment"
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
---
|
| 192 |
+
|
| 193 |
+
### Task 3: Create the Dockerfile
|
| 194 |
+
|
| 195 |
+
**Files:**
|
| 196 |
+
- Create: `Dockerfile`
|
| 197 |
+
|
| 198 |
+
Create `Dockerfile` with these exact contents:
|
| 199 |
+
|
| 200 |
+
```dockerfile
|
| 201 |
+
# EuropaLex β Docker / Hugging Face Spaces Deployment
|
| 202 |
+
# Single-stage build: CPU-only deps + model download + Gradio launch
|
| 203 |
+
#
|
| 204 |
+
# Build:
|
| 205 |
+
# docker build --secret id=hf_token,env=HUGGING_FACE_HUB_TOKEN -t europalex .
|
| 206 |
+
#
|
| 207 |
+
# Run (local test):
|
| 208 |
+
# docker run -p 7860:7860 europalex
|
| 209 |
+
|
| 210 |
+
FROM python:3.12-slim
|
| 211 |
+
|
| 212 |
+
# βββ System dependencies βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 213 |
+
# git for huggingface-cli, build-essential for llama-cpp-python compilation
|
| 214 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 215 |
+
git \
|
| 216 |
+
build-essential \
|
| 217 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 218 |
+
|
| 219 |
+
# βββ Build secret: Hugging Face token ββββββββββββββββββββββββββββββββββ
|
| 220 |
+
# Mounted at /run/secrets/hf_token during docker build.
|
| 221 |
+
# Used to authenticate huggingface-cli download of gated models.
|
| 222 |
+
RUN --mount=type=secret,id=hf_token \
|
| 223 |
+
if [ -f /run/secrets/hf_token ]; then \
|
| 224 |
+
echo "Authenticated as $(huggingface-cli whoami --token $(cat /run/secrets/hf_token) 2>/dev/null || echo 'unknown')"; \
|
| 225 |
+
else \
|
| 226 |
+
echo "WARNING: No HUGGING_FACE_HUB_TOKEN secret provided. Model download will fail for gated models."; \
|
| 227 |
+
fi
|
| 228 |
+
|
| 229 |
+
# βββ CPU-only PyTorch ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 230 |
+
# Install from the official CPU wheel index to avoid ~2 GB of CUDA deps.
|
| 231 |
+
RUN pip install --no-cache-dir \
|
| 232 |
+
--extra-index-url https://download.pytorch.org/whl/cpu \
|
| 233 |
+
torch>=2.1.0
|
| 234 |
+
|
| 235 |
+
# βββ llama-cpp-python (CPU backend, no CUDA flags) βββββββββββββββββββββ
|
| 236 |
+
# Build with default CPU backend β no CUDA/MKL flags needed on slim base.
|
| 237 |
+
RUN pip install --no-cache-dir \
|
| 238 |
+
llama-cpp-python>=0.3.28
|
| 239 |
+
|
| 240 |
+
# βββ Other Python dependencies βββββββββββββββββββββοΏ½οΏ½οΏ½βββββββββββββββββββ
|
| 241 |
+
COPY requirements.txt .
|
| 242 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 243 |
+
|
| 244 |
+
# βββ Copy project source βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 245 |
+
COPY . /app
|
| 246 |
+
WORKDIR /app
|
| 247 |
+
|
| 248 |
+
# βββ Download models at build time βββββββββββββββββββββββββββββββββββββ
|
| 249 |
+
# Uses the HF token secret for authenticated downloads.
|
| 250 |
+
# Models are baked into the image (~26 GB total).
|
| 251 |
+
RUN --mount=type=secret,id=hf_token \
|
| 252 |
+
if [ -f /run/secrets/hf_token ]; then \
|
| 253 |
+
export HUGGING_FACE_HUB_TOKEN=$(cat /run/secrets/hf_token); \
|
| 254 |
+
fi && \
|
| 255 |
+
python -m models.download_models
|
| 256 |
+
|
| 257 |
+
# βββ Launch ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 258 |
+
EXPOSE 7860
|
| 259 |
+
CMD ["python", "app.py"]
|
| 260 |
+
```
|
| 261 |
+
|
| 262 |
+
- [ ] **Step 1: Write the Dockerfile**
|
| 263 |
+
|
| 264 |
+
Write the content above to `Dockerfile`.
|
| 265 |
+
|
| 266 |
+
- [ ] **Step 2: Verify syntax**
|
| 267 |
+
|
| 268 |
+
Run:
|
| 269 |
+
```bash
|
| 270 |
+
# Check Dockerfile syntax (requires docker)
|
| 271 |
+
docker build --check -f Dockerfile . 2>&1 || echo "Docker not available β manual review required"
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
If Docker is not available, do a manual review:
|
| 275 |
+
- Verify `FROM python:3.12-slim` is the first instruction
|
| 276 |
+
- Verify `--mount=type=secret,id=hf_token` appears in both the auth check and model download steps
|
| 277 |
+
- Verify CPU-only PyTorch uses `--extra-index-url https://download.pytorch.org/whl/cpu`
|
| 278 |
+
- Verify `EXPOSE 7860` and `CMD ["python", "app.py"]` are at the end
|
| 279 |
+
|
| 280 |
+
- [ ] **Step 3: Commit**
|
| 281 |
+
|
| 282 |
+
```bash
|
| 283 |
+
git add Dockerfile
|
| 284 |
+
git commit -m "feat: add Dockerfile for HF Spaces deployment (CPU-only)"
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
### Task 4: Create `.dockerignore`
|
| 290 |
+
|
| 291 |
+
**Files:**
|
| 292 |
+
- Create: `.dockerignore`
|
| 293 |
+
|
| 294 |
+
Create `.dockerignore` with these exact contents:
|
| 295 |
+
|
| 296 |
+
```dockerignore
|
| 297 |
+
# βββ Version control βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 298 |
+
.git/
|
| 299 |
+
.gitignore
|
| 300 |
+
|
| 301 |
+
# βββ Virtual environment (not needed β Docker installs deps) βββββββββββ
|
| 302 |
+
.venv/
|
| 303 |
+
venv/
|
| 304 |
+
|
| 305 |
+
# βββ Python cache ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 306 |
+
__pycache__/
|
| 307 |
+
*.pyc
|
| 308 |
+
*.pyo
|
| 309 |
+
.pytest_cache/
|
| 310 |
+
|
| 311 |
+
# βββ Model weights β downloaded inside Dockerfile, NOT from build context
|
| 312 |
+
.local/models/
|
| 313 |
+
|
| 314 |
+
# βββ Gradio cache βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 315 |
+
.gradio/
|
| 316 |
+
|
| 317 |
+
# βββ OS files ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 318 |
+
.DS_Store
|
| 319 |
+
Thumbs.db
|
| 320 |
+
|
| 321 |
+
# βββ Test output files (from tests/test_outputs/) ββββββββββββββββββββββ
|
| 322 |
+
tests/test_outputs/
|
| 323 |
+
|
| 324 |
+
# βββ Lock file β not needed inside container (pip install handles it) ββ
|
| 325 |
+
uv.lock
|
| 326 |
+
|
| 327 |
+
# βββ Docs / plans (not needed at runtime) ββββββββββββββββββββββββββββββ
|
| 328 |
+
docs/
|
| 329 |
+
|
| 330 |
+
# βββ Agent conventions (not needed at runtime) βββββββββββββββββββββββββ
|
| 331 |
+
AGENTS.md
|
| 332 |
+
```
|
| 333 |
+
|
| 334 |
+
- [ ] **Step 1: Write the .dockerignore**
|
| 335 |
+
|
| 336 |
+
Write the content above to `.dockerignore`.
|
| 337 |
+
|
| 338 |
+
- [ ] **Step 2: Verify it excludes key paths**
|
| 339 |
+
|
| 340 |
+
Run:
|
| 341 |
+
```bash
|
| 342 |
+
grep -c "local/models" .dockerignore # Should be 1
|
| 343 |
+
grep -c ".venv" .dockerignore # Should be >= 1
|
| 344 |
+
grep -c "__pycache__" .dockerignore # Should be 1
|
| 345 |
+
grep -c "docs/" .dockerignore # Should be 1
|
| 346 |
+
```
|
| 347 |
+
|
| 348 |
+
- [ ] **Step 3: Commit**
|
| 349 |
+
|
| 350 |
+
```bash
|
| 351 |
+
git add .dockerignore
|
| 352 |
+
git commit -m "feat: add .dockerignore for HF Spaces deployment"
|
| 353 |
+
```
|
| 354 |
+
|
| 355 |
+
---
|
| 356 |
+
|
| 357 |
+
### Task 5: Final Verification
|
| 358 |
+
|
| 359 |
+
**Files:**
|
| 360 |
+
- All created files above
|
| 361 |
+
|
| 362 |
+
- [ ] **Step 1: Verify all files exist and are non-empty**
|
| 363 |
+
|
| 364 |
+
Run:
|
| 365 |
+
```bash
|
| 366 |
+
for f in Dockerfile .dockerignore README.md docs/europalex-readme-old.md; do
|
| 367 |
+
echo "$f: $(wc -l < $f) lines, $(wc -c < $f) bytes"
|
| 368 |
+
done
|
| 369 |
+
```
|
| 370 |
+
|
| 371 |
+
Expected output:
|
| 372 |
+
- `Dockerfile`: ~70 lines
|
| 373 |
+
- `.dockerignore`: ~35 lines
|
| 374 |
+
- `README.md`: ~120 lines (new deployment-focused version)
|
| 375 |
+
- `docs/europalex-readme-old.md`: ~180 lines (archived original)
|
| 376 |
+
|
| 377 |
+
- [ ] **Step 2: Verify the Dockerfile references correct paths**
|
| 378 |
+
|
| 379 |
+
Run:
|
| 380 |
+
```bash
|
| 381 |
+
grep "models.download_models" Dockerfile # Should find the download command
|
| 382 |
+
grep "requirements.txt" Dockerfile # Should find COPY + pip install
|
| 383 |
+
grep "EXPOSE 7860" Dockerfile # Should find port exposure
|
| 384 |
+
grep 'CMD \["python"' Dockerfile # Should find launch command
|
| 385 |
+
```
|
| 386 |
+
|
| 387 |
+
- [ ] **Step 3: Verify .dockerignore does NOT exclude files needed at runtime**
|
| 388 |
+
|
| 389 |
+
Run:
|
| 390 |
+
```bash
|
| 391 |
+
# These should NOT appear in .dockerignore (they are needed):
|
| 392 |
+
grep "app.py" .dockerignore && echo "FAIL: app.py excluded" || echo "OK: app.py not excluded"
|
| 393 |
+
grep "core/" .dockerignore && echo "FAIL: core/ excluded" || echo "OK: core/ not excluded"
|
| 394 |
+
grep "frontend/" .dockerignore && echo "FAIL: frontend/ excluded" || echo "OK: frontend/ not excluded"
|
| 395 |
+
```
|
| 396 |
+
|
| 397 |
+
- [ ] **Step 4: Final commit (if any uncommitted changes remain)**
|
| 398 |
+
|
| 399 |
+
```bash
|
| 400 |
+
git status
|
| 401 |
+
git add -A
|
| 402 |
+
git diff --cached --stat
|
| 403 |
+
git commit -m "feat: Docker/HF Spaces deployment β Dockerfile, .dockerignore, README rewrite"
|
| 404 |
+
```
|
| 405 |
+
|
| 406 |
+
---
|
| 407 |
+
|
| 408 |
+
## Self-Review Checklist
|
| 409 |
+
|
| 410 |
+
1. **Spec coverage:** All four spec requirements are addressed β `Dockerfile` (Task 3), `.dockerignore` (Task 4), `README.md` replacement (Task 2), archived README (Task 1). No code changes needed because `_auto_download_models()` already skips download when GGUF files exist.
|
| 411 |
+
|
| 412 |
+
2. **Placeholder scan:** No placeholders found. All file contents are complete with exact text.
|
| 413 |
+
|
| 414 |
+
3. **Type consistency:** N/A β no code changes, only infrastructure and documentation files.
|
| 415 |
+
|
| 416 |
+
4. **Dockerfile correctness:**
|
| 417 |
+
- `python:3.12-slim` base β
|
| 418 |
+
- Build-essential for llama-cpp-python compilation β
|
| 419 |
+
- CPU-only PyTorch via `--extra-index-url https://download.pytorch.org/whl/cpu` β
|
| 420 |
+
- `llama-cpp-python>=0.3.28` without CUDA flags β
|
| 421 |
+
- HF token build secret (`--mount=type=secret,id=hf_token`) β
|
| 422 |
+
- Model download via `python -m models.download_models` at build time β
|
| 423 |
+
- `EXPOSE 7860` and `CMD ["python", "app.py"]` β
|
| 424 |
+
|
| 425 |
+
5. **`.dockerignore` correctness:**
|
| 426 |
+
- Excludes `.local/models/` (models downloaded inside Dockerfile) β
|
| 427 |
+
- Excludes `.venv/`, `__pycache__/`, test outputs, docs β
|
| 428 |
+
- Does NOT exclude `core/`, `frontend/`, `app.py`, `configs/` β
|
docs/superpowers/specs/2026-06-14-europalex-docker-hf-spaces-deployment-design.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EuropaLex β Docker / Hugging Face Spaces Deployment
|
| 2 |
+
|
| 3 |
+
**Date:** 2026-06-14
|
| 4 |
+
**Status:** Approved
|
| 5 |
+
**Scope:** Containerize EuropaLex for deployment on Hugging Face Spaces (CPU tier)
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
EuropaLex is a Gradio-based flashcard generator that uses four local AI models (MiniCPM5-1B, tiny-aya-water, OmniVoice, FLUX.2-klein). This design specifies how to deploy it as a Docker container on Hugging Face Spaces with CPU-only inference, accepting slower performance in exchange for zero-cost hosting.
|
| 10 |
+
|
| 11 |
+
## Design Decisions
|
| 12 |
+
|
| 13 |
+
### 1. Keep All Four Models
|
| 14 |
+
All four models are retained: MiniCPM5-1B (text generation), tiny-aya-water (translation), OmniVoice (TTS), FLUX.2-klein (image generation). CPU inference will be slow β expect 30+ seconds per sentence for translation and longer for TTS/image generation β but all features remain functional.
|
| 15 |
+
|
| 16 |
+
### 2. Model Download at Docker Build Time
|
| 17 |
+
Models are downloaded during the `docker build` step, not at runtime. The resulting image is ~20β30 GB but the app starts instantly when deployed to HF Spaces. This avoids timeout issues with HF Spaces' build limits and means users don't wait minutes on first visit.
|
| 18 |
+
|
| 19 |
+
### 3. Single-Stage Dockerfile
|
| 20 |
+
A single `Dockerfile` handles everything: base image, dependency installation, model download, and launch. No multi-stage complexity. HF Spaces handles large images without issue.
|
| 21 |
+
|
| 22 |
+
### 4. Build-Time Hugging Face Token
|
| 23 |
+
The `HUGGING_FACE_HUB_TOKEN` is passed as a Docker build secret (`--secret id=hf_token`). This authenticates the `huggingface-cli download` step during build time so models are baked into the image. No runtime authentication is needed.
|
| 24 |
+
|
| 25 |
+
### 5. CPU-Only PyTorch
|
| 26 |
+
PyTorch is installed from the official CPU wheel index (`https://download.pytorch.org/whl/cpu`) to avoid pulling in ~2 GB of CUDA dependencies. llama-cpp-python is built with the default CPU backend (no CUDA flags). diffusers and omnivoice fall back to CPU automatically when no GPU is available.
|
| 27 |
+
|
| 28 |
+
## Files to Create / Modify
|
| 29 |
+
|
| 30 |
+
| File | Action | Purpose |
|
| 31 |
+
|---|---|---|
|
| 32 |
+
| `Dockerfile` | **Create** | Single-stage build: deps + model download + Gradio launch |
|
| 33 |
+
| `.dockerignore` | **Create** | Exclude `.venv/`, `.git/`, `__pycache__/`, test outputs, large binaries. Explicitly exclude `.local/models/` from build context (models downloaded inside Dockerfile). |
|
| 34 |
+
| `README.md` | **Replace** | New README focused on HF Spaces deployment instructions |
|
| 35 |
+
| `docs/europalex-readme-old.md` | **Create** | Archive of current README for reference |
|
| 36 |
+
|
| 37 |
+
## Data Flow
|
| 38 |
+
|
| 39 |
+
```
|
| 40 |
+
Docker build:
|
| 41 |
+
python:3.12-slim β pip install deps β huggingface-cli login (build secret) β download models β CMD ["python", "app.py"]
|
| 42 |
+
|
| 43 |
+
HF Spaces runtime:
|
| 44 |
+
Container starts β _auto_download_models() checks .local/models/ β finds GGUF files β skips download β launches Gradio on :7860
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
## Error Handling
|
| 48 |
+
|
| 49 |
+
- **Model download failure at build time:** Docker build fails with a clear error. User knows immediately, not after deploying to HF Spaces.
|
| 50 |
+
- **Missing HF token:** Build fails with `huggingface-cli` auth error. Documented in README as a known setup step.
|
| 51 |
+
- **CPU OOM at runtime:** If RAM is insufficient, PyTorch/llama-cpp-python will fail gracefully. `app.py` already has try/except around engine initialization that surfaces errors to the Gradio UI.
|
| 52 |
+
|
| 53 |
+
## HF Spaces Configuration (Documented in New README)
|
| 54 |
+
|
| 55 |
+
User workflow:
|
| 56 |
+
1. Create a new Hugging Face Space β select **Docker** framework
|
| 57 |
+
2. Upload `Dockerfile`, `.dockerignore`, and project files
|
| 58 |
+
3. Set secret `HUGGING_FACE_HUB_TOKEN` in Space settings (under Secrets)
|
| 59 |
+
4. Push to repo β HF builds the image automatically
|
| 60 |
+
|
| 61 |
+
## Constraints
|
| 62 |
+
|
| 63 |
+
- **CPU performance:** All inference runs on CPU. Translation of a batch of 3 sentences may take 1β3 minutes. TTS and image generation are slower still. This is acceptable for a free-tier demo.
|
| 64 |
+
- **Image size:** ~20β30 GB due to model weights baked in. HF Spaces handles this, but the initial build takes longer (5β15 minutes).
|
| 65 |
+
- **No GPU acceleration:** FLUX.2-klein and OmniVoice are designed for GPU. CPU inference will be very slow but functional.
|