File size: 3,908 Bytes
519b145 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
"""
Modal deployment script for Hue Portal Backend (Django)
Based on flux-modal pattern: https://github.com/omarkortam/flux-modal
"""
import os
import subprocess
from pathlib import Path
import modal
# Build Docker image with all dependencies
hue_backend_image = (
modal.Image.debian_slim(python_version="3.11")
.apt_install(
"git",
"build-essential",
"tesseract-ocr",
"tesseract-ocr-eng",
"tesseract-ocr-vie",
"libpoppler-cpp-dev",
"pkg-config",
"libgl1",
)
.pip_install(
"Django==5.0.6",
"djangorestframework==3.15.2",
"django-cors-headers==4.4.0",
"psycopg2-binary==2.9.9",
"django-environ==0.11.2",
"gunicorn==22.0.0",
"whitenoise==6.6.0",
"redis==5.0.6",
"celery==5.4.0",
"scikit-learn==1.3.2",
"numpy==1.24.3",
"scipy==1.11.4",
"pydantic>=2.0.0,<3.0.0",
"sentence-transformers>=2.2.0",
"torch>=2.0.0",
"faiss-cpu>=1.7.4",
"python-docx==0.8.11",
"PyMuPDF==1.24.3",
"Pillow>=8.0.0,<12.0",
"pytesseract==0.3.13",
"requests>=2.31.0",
)
# Copy backend code
.copy_local_dir("backend", "/app")
.run_commands(
"mkdir -p /app/hue_portal/static /app/hue_portal/media",
"chmod +x /app/hue_portal/manage.py",
)
)
app = modal.App(name="hue-portal-backend", image=hue_backend_image)
# Mount backend directory
backend_mount = modal.Mount.from_local_dir("backend", remote_path="/app")
@app.function(
allow_concurrent_inputs=100,
concurrency_limit=10,
container_idle_timeout=300, # 5 minutes
timeout=600, # 10 minutes max request time
cpu=2, # 2 CPUs
memory=4096, # 4GB RAM
secrets=[
modal.Secret.from_name("hue-portal-secrets", required=False), # Optional for now
],
mounts=[backend_mount],
)
@modal.web_server(8000, startup_timeout=180)
def web():
"""Start Django application with Gunicorn."""
import os
# Set working directory
os.chdir("/app/hue_portal")
# Run migrations
print("[Modal] Running migrations...")
migrate_result = subprocess.run(
["python", "manage.py", "migrate", "--noinput"],
capture_output=True,
text=True,
check=False,
)
if migrate_result.returncode != 0:
print(f"[Modal] Migration warning: {migrate_result.stderr[:200]}")
else:
print("[Modal] Migrations completed")
# Collect static files
print("[Modal] Collecting static files...")
collect_result = subprocess.run(
["python", "manage.py", "collectstatic", "--noinput"],
capture_output=True,
text=True,
check=False,
)
if collect_result.returncode != 0:
print(f"[Modal] Collectstatic warning: {collect_result.stderr[:200]}")
else:
print("[Modal] Static files collected")
# Start Gunicorn
print("[Modal] Starting Gunicorn on 0.0.0.0:8000...")
gunicorn_process = subprocess.Popen(
[
"gunicorn",
"-b", "0.0.0.0:8000",
"--workers", "2", # Reduced for Modal
"--timeout", "120",
"--access-logfile", "-",
"--error-logfile", "-",
"hue_portal.wsgi:application",
],
cwd="/app/hue_portal",
)
print("[Modal] Gunicorn started, waiting...")
# Keep process alive and monitor
import time
try:
while True:
# Check if gunicorn is still running
if gunicorn_process.poll() is not None:
print(f"[Modal] Gunicorn exited with code {gunicorn_process.returncode}")
break
time.sleep(1)
except KeyboardInterrupt:
print("[Modal] Shutting down...")
gunicorn_process.terminate()
gunicorn_process.wait()
|