# ============================================================ # Dify 1.14.2 — Hugging Face Space (Docker) # Port HF : 7860 # Mode : single container # Base de données : Supabase (PostgreSQL externe) # Redis : Upstash (TCP/TLS externe) # Vecteurs : pgvector via Supabase (direct port 5432) # Orchestreur : supervisord (nginx + api + worker + web + plugin_daemon) # ============================================================ # ── Stage 1 : Build frontend Next.js ──────────────────────── FROM node:20-slim AS web-builder RUN apt-get update && apt-get install -y --no-install-recommends \ git ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN git clone --depth 1 --branch 1.14.2 \ https://github.com/langgenius/dify.git /tmp/dify \ && cp -r /tmp/dify/web /app/web \ && rm -rf /tmp/dify WORKDIR /app/web RUN npm install -g pnpm \ && pnpm install --frozen-lockfile \ && pnpm build # ── Stage final ────────────────────────────────────────────── FROM python:3.12-slim-bookworm # -------------------------------------------------- # Dépendances système # -------------------------------------------------- RUN apt-get update && apt-get install -y --no-install-recommends \ git curl ca-certificates gnupg \ nginx supervisor \ libpq-dev libmagic1 libcairo2 libglib2.0-0 \ poppler-utils tesseract-ocr \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* # -------------------------------------------------- # Cloner et installer Dify API # -------------------------------------------------- RUN git clone --depth 1 --branch 1.14.2 \ https://github.com/langgenius/dify.git /tmp/dify \ && cp -r /tmp/dify/api /app/api \ && rm -rf /tmp/dify WORKDIR /app/api RUN pip install --no-cache-dir uv==0.6.14 \ && uv sync --frozen --no-dev # -------------------------------------------------- # Plugin daemon (binaire Go) # -------------------------------------------------- RUN curl -L \ "https://github.com/langgenius/dify-plugin-daemon/releases/download/0.1.0/dify-plugin-daemon-linux-amd64" \ -o /app/plugin_daemon \ && chmod +x /app/plugin_daemon # -------------------------------------------------- # Frontend (depuis le stage web-builder) # -------------------------------------------------- COPY --from=web-builder /app/web/.next/standalone /app/web COPY --from=web-builder /app/web/.next/static /app/web/.next/static COPY --from=web-builder /app/web/public /app/web/public # -------------------------------------------------- # Variables non-secrètes # # ⚠️ NE PAS mettre ici (→ HF Secrets obligatoire) : # - SECRET_KEY clé de chiffrement Dify # - DB_PASSWORD mot de passe Supabase # - PGVECTOR_PASSWORD même mot de passe Supabase # - REDIS_PASSWORD token Upstash # - CELERY_BROKER_URL URL complète Upstash avec token # - PLUGIN_DAEMON_KEY clé plugin daemon # - INNER_API_KEY_FOR_PLUGIN clé interne plugins # - CONSOLE_API_URL URL publique du Space HF # - APP_API_URL URL publique du Space HF # - APP_WEB_URL URL publique du Space HF # - SERVICE_API_URL URL publique du Space HF # # -------------------------------------------------- ENV \ # --- Python --- PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ MALLOC_ARENA_MAX=2 \ \ # --- Node --- NODE_OPTIONS="--max-old-space-size=2048" \ \ # --- Base de données (Supabase / pooler port 6543) --- DB_HOST=aws-1-eu-central-1.pooler.supabase.com \ DB_PORT=6543 \ DB_USERNAME=postgres.bumhiinnvlbrpmvjpuwu \ DB_DATABASE=postgres \ \ # --- pgvector (connexion directe port 5432 — pooler incompatible) --- PGVECTOR_HOST=db.bumhiinnvlbrpmvjpuwu.supabase.co \ PGVECTOR_PORT=5432 \ PGVECTOR_USER=postgres \ PGVECTOR_DATABASE=postgres \ VECTOR_STORE=pgvector \ \ # --- Redis (Upstash / TLS) --- REDIS_HOST=fitting-wildcat-109052.upstash.io \ REDIS_PORT=6379 \ REDIS_USERNAME=default \ REDIS_USE_SSL=true \ \ # --- Workers (réduits pour économiser RAM) --- SERVER_WORKER_AMOUNT=1 \ CELERY_WORKER_AMOUNT=1 \ \ # --- Stockage fichiers --- STORAGE_TYPE=local \ STORAGE_LOCAL_PATH=/app/api/storage \ \ # --- Plugin daemon --- PLUGIN_DAEMON_URL=http://127.0.0.1:5002 \ \ # --- Sandbox désactivé (économise 2 services) --- CODE_EXECUTION_ENABLED=false \ \ # --- Migrations auto au démarrage --- MIGRATION_ENABLED=true \ \ # --- Logs --- LOG_LEVEL=INFO \ LOG_FILE=/app/logs/server.log \ \ # --- Deploy --- DEPLOY_ENV=PRODUCTION \ FLASK_APP=app.py \ \ # --- Timezone --- TZ=Europe/Berlin \ GENERIC_TIMEZONE=Europe/Berlin # -------------------------------------------------- # Dossiers nécessaires # -------------------------------------------------- RUN mkdir -p /app/api/storage /app/logs /var/log/supervisor /run/nginx # -------------------------------------------------- # nginx.conf # -------------------------------------------------- RUN cat > /etc/nginx/nginx.conf << 'EOF' user www-data; worker_processes 1; pid /run/nginx.pid; events { worker_connections 512; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; client_max_body_size 100M; server { listen 7860; server_name _; location ~ ^/(console/api|api|v1|files|explore) { proxy_pass http://127.0.0.1:5001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 300s; proxy_buffering off; proxy_cache off; } location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } } EOF # -------------------------------------------------- # supervisord.conf # -------------------------------------------------- RUN cat > /etc/supervisor/conf.d/dify.conf << 'EOF' [supervisord] nodaemon=true logfile=/var/log/supervisor/supervisord.log childlogdir=/var/log/supervisor [program:nginx] command=nginx -g "daemon off;" autostart=true autorestart=true priority=10 stdout_logfile=/var/log/supervisor/nginx.log stderr_logfile=/var/log/supervisor/nginx.err.log [program:plugin_daemon] command=/app/plugin_daemon directory=/app autostart=true autorestart=true priority=20 stdout_logfile=/var/log/supervisor/plugin_daemon.log stderr_logfile=/var/log/supervisor/plugin_daemon.err.log [program:api] command=uv run gunicorn \ --bind 127.0.0.1:5001 \ --workers 1 \ --worker-class geventwebsocket.gunicorn.workers.GeventWebSocketWorker \ --timeout 200 \ app:app directory=/app/api autostart=true autorestart=true priority=30 stdout_logfile=/var/log/supervisor/api.log stderr_logfile=/var/log/supervisor/api.err.log [program:worker] command=uv run celery \ -A app.celery worker \ --concurrency 1 \ --loglevel INFO \ -Q dataset,generation,mail,ops_trace,app_deletion directory=/app/api autostart=true autorestart=true priority=30 stdout_logfile=/var/log/supervisor/worker.log stderr_logfile=/var/log/supervisor/worker.err.log [program:web] command=node server.js directory=/app/web autostart=true autorestart=true priority=40 environment=PORT="3000",HOSTNAME="127.0.0.1" stdout_logfile=/var/log/supervisor/web.log stderr_logfile=/var/log/supervisor/web.err.log EOF # -------------------------------------------------- # entrypoint.sh # -------------------------------------------------- RUN cat > /entrypoint.sh << 'EOF' #!/bin/bash set -e echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " Dify 1.14.2 — démarrage" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" cd /app/api for i in 1 2 3 4 5; do uv run flask db upgrade && break echo "[init] Tentative $i échouée, retry dans 5s..." sleep 5 done echo "[init] Migrations OK" exec supervisord -c /etc/supervisor/conf.d/dify.conf EOF RUN chmod +x /entrypoint.sh EXPOSE 7860 CMD ["/entrypoint.sh"]