| |
| FROM python:3.12-slim-bookworm AS base |
|
|
| |
| ENV POETRY_VERSION=1.8.4 \ |
| POETRY_NO_INTERACTION=1 \ |
| POETRY_VIRTUALENVS_CREATE=true \ |
| POETRY_VIRTUALENVS_IN_PROJECT=true \ |
| POETRY_CACHE_DIR=/tmp/poetry_cache \ |
| PYTHONDONTWRITEBYTECODE=1 \ |
| LANG=en_US.UTF-8 \ |
| LANGUAGE=en_US:en \ |
| LC_ALL=en_US.UTF-8 \ |
| PORT=7860 \ |
| NODE_ENV=production |
|
|
| |
| RUN useradd -m -u 1000 user |
|
|
| |
| RUN apt-get update && apt-get install -y \ |
| curl \ |
| git \ |
| gcc \ |
| python3-dev \ |
| libgmp-dev \ |
| libmpfr-dev \ |
| libmpc-dev \ |
| nodejs \ |
| npm \ |
| postgresql \ |
| postgresql-contrib \ |
| locales \ |
| nginx \ |
| netcat-openbsd \ |
| net-tools \ |
| procps \ |
| psmisc \ |
| && rm -rf /var/lib/apt/lists/* \ |
| && pip install --no-cache-dir "poetry==${POETRY_VERSION}" \ |
| && sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \ |
| && locale-gen |
|
|
| |
| RUN rm /etc/nginx/sites-enabled/default || true |
| COPY <<EOF /etc/nginx/nginx.conf |
| user user; |
| pid /run/nginx.pid; |
| worker_processes auto; |
|
|
| events { |
| worker_connections 1024; |
| } |
|
|
| http { |
| include /etc/nginx/mime.types; |
| default_type application/octet-stream; |
| sendfile on; |
| keepalive_timeout 120; |
| client_body_temp_path /var/lib/nginx/body; |
| proxy_temp_path /var/lib/nginx/proxy; |
| fastcgi_temp_path /var/lib/nginx/fastcgi; |
| uwsgi_temp_path /var/lib/nginx/uwsgi; |
| scgi_temp_path /var/lib/nginx/scgi; |
| |
| |
| proxy_buffer_size 128k; |
| proxy_buffers 4 256k; |
| proxy_busy_buffers_size 256k; |
| |
| |
| proxy_connect_timeout 120s; |
| proxy_read_timeout 120s; |
| proxy_send_timeout 120s; |
|
|
| upstream frontend { |
| server 127.0.0.1:3000; |
| } |
|
|
| upstream backend { |
| server 127.0.0.1:5001; |
| } |
|
|
| server { |
| listen 7860; |
| server_name _; |
| client_max_body_size 100M; |
| |
| |
| proxy_buffer_size 128k; |
| proxy_buffers 4 256k; |
| proxy_busy_buffers_size 256k; |
| |
| location / { |
| proxy_pass http://frontend; |
| proxy_http_version 1.1; |
| proxy_set_header Upgrade "$http_upgrade"; |
| proxy_set_header Connection "upgrade"; |
| proxy_set_header Host "$host"; |
| proxy_cache_bypass "$http_upgrade"; |
| proxy_set_header X-Real-IP "$remote_addr"; |
| proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for"; |
| proxy_set_header X-Forwarded-Proto "$scheme"; |
| proxy_read_timeout 300s; |
| proxy_connect_timeout 120s; |
| proxy_send_timeout 120s; |
| } |
| |
| location /api { |
| proxy_pass http://backend; |
| proxy_http_version 1.1; |
| proxy_set_header Upgrade "$http_upgrade"; |
| proxy_set_header Connection "upgrade"; |
| proxy_set_header Host "$host"; |
| proxy_cache_bypass "$http_upgrade"; |
| proxy_set_header X-Real-IP "$remote_addr"; |
| proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for"; |
| proxy_set_header X-Forwarded-Proto "$scheme"; |
| proxy_read_timeout 300s; |
| proxy_connect_timeout 120s; |
| proxy_send_timeout 120s; |
| } |
|
|
| access_log /var/log/nginx/access.log; |
| error_log /var/log/nginx/error.log debug; |
| } |
| } |
| EOF |
|
|
| |
| RUN mkdir -p /var/log/nginx /var/run/postgresql /var/lib/postgresql/data /var/log/postgresql /app/api /app/web /data/storage && \ |
| mkdir -p /var/lib/nginx/body /var/lib/nginx/proxy /var/lib/nginx/fastcgi /var/lib/nginx/uwsgi /var/lib/nginx/scgi && \ |
| chown -R root:root /etc/nginx && \ |
| chmod -R 755 /etc/nginx && \ |
| touch /run/nginx.pid && \ |
| chown -R root:root /run/nginx.pid && \ |
| chown -R user:user /var/lib/nginx && \ |
| chmod -R 755 /var/lib/nginx && \ |
| chmod -R 777 /run && \ |
| chmod 700 /var/lib/postgresql/data && \ |
| chmod 777 /data /app && \ |
| chown -R user:user /var/run/postgresql /var/lib/postgresql/data /var/log/postgresql /app /data /var/log/nginx |
|
|
| |
| COPY <<-'EOT' /app/entrypoint.sh |
| |
| set -e |
| echo "===== Application Startup at $(date "+%Y-%m-%d %H:%M:%S") =====" |
|
|
| |
| check_service() { |
| local service=$1 |
| local url=$2 |
| local max_attempts=$3 |
| local wait_time=$4 |
| |
| echo "Checking $service..." |
| for i in $(seq 1 $max_attempts); do |
| if curl -s "$url" >/dev/null 2>&1; then |
| echo "$service is ready" |
| return 0 |
| fi |
| echo "Waiting for $service (attempt $i/$max_attempts)..." |
| sleep $wait_time |
| done |
| echo "$service failed to start" |
| return 1 |
| } |
|
|
| |
| wait_for_port() { |
| local port=$1 |
| local service=$2 |
| local max_attempts=$3 |
| local wait_time=$4 |
| local host=${5:-localhost} |
|
|
| echo "Waiting for $service on $host:$port..." |
| for i in $(seq 1 $max_attempts); do |
| if nc -z $host $port; then |
| echo "$service is listening on $host:$port" |
| return 0 |
| fi |
| echo "Waiting for $service (attempt $i/$max_attempts)..." |
| sleep $wait_time |
| done |
| |
| |
| echo "=== $service Startup Failure ===" |
| echo "Service failed to bind to $host:$port after $max_attempts attempts" |
| echo "Checking process status:" |
| ps aux | grep -i "$service" || true |
| echo "Checking port status:" |
| netstat -tulpn | grep ":$port" || true |
| echo "Checking logs:" |
| if [ "$service" = "Frontend" ]; then |
| tail -n 50 /app/web/output.log || true |
| fi |
| return 1 |
| } |
|
|
| |
| start_frontend() { |
| cd /app/web |
| echo "Cleaning up any existing Node.js processes..." |
| |
| pkill -9 -f "node server.js" || true |
| |
| sleep 2 |
| |
| if netstat -tulpn | grep ":3000" > /dev/null; then |
| echo "Port 3000 is still in use. Attempting to force cleanup..." |
| |
| fuser -k 3000/tcp || true |
| sleep 2 |
| fi |
| |
| |
| touch output.log |
| echo "Starting Next.js server at $(date)" > output.log |
| |
| |
| export HOSTNAME="0.0.0.0" |
| export NODE_ENV=production |
| export PORT=3000 |
| |
| |
| node server.js >> output.log 2>&1 & |
| local pid=$! |
| echo "Frontend server started with PID: $pid" |
| |
| |
| sleep 5 |
| if ! kill -0 $pid 2>/dev/null; then |
| echo "Frontend server failed to start. Last 50 lines of logs:" |
| tail -n 50 output.log |
| return 1 |
| fi |
| |
| |
| if ! netstat -tulpn | grep ":3000.*$pid" > /dev/null; then |
| echo "Frontend server is running but not bound to port 3000. Last 50 lines of logs:" |
| tail -n 50 output.log |
| return 1 |
| fi |
| |
| return 0 |
| } |
|
|
| |
| monitor_services() { |
| local restart_count=0 |
| local max_restarts=3 |
| |
| while true; do |
| if ! pgrep -f "gunicorn" > /dev/null; then |
| echo "API server died" |
| tail -n 100 /app/api/gunicorn.error.log || true |
| exit 1 |
| fi |
| |
| if ! pgrep -f "node server.js" > /dev/null; then |
| echo "Frontend server died. Attempting restart..." |
| if [ $restart_count -lt $max_restarts ]; then |
| restart_count=$((restart_count + 1)) |
| echo "Restart attempt $restart_count of $max_restarts" |
| |
| pkill -9 -f "node server.js" || true |
| fuser -k 3000/tcp || true |
| sleep 2 |
| if start_frontend; then |
| echo "Frontend server restarted successfully" |
| |
| restart_count=0 |
| else |
| echo "Failed to restart frontend server" |
| tail -n 100 /app/web/output.log || true |
| exit 1 |
| fi |
| else |
| echo "Maximum frontend restart attempts reached" |
| tail -n 100 /app/web/output.log || true |
| exit 1 |
| fi |
| fi |
| |
| if ! pgrep -f "nginx" > /dev/null; then |
| echo "Nginx died" |
| tail -n 100 /var/log/nginx/error.log || true |
| exit 1 |
| fi |
| sleep 5 |
| done |
| } |
|
|
| |
| if [ ! -f "$PGDATA/PG_VERSION" ]; then |
| echo "Initializing PostgreSQL database..." |
| initdb --username=user --pwfile=<(echo "$DB_PASSWORD") --auth=md5 --encoding=UTF8 |
| |
| |
| echo "local all all trust" > "$PGDATA/pg_hba.conf" |
| echo "host all all 127.0.0.1/32 md5" >> "$PGDATA/pg_hba.conf" |
| echo "host all all ::1/128 md5" >> "$PGDATA/pg_hba.conf" |
| echo "host all all 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf" |
| |
| echo "listen_addresses = '*'" >> "$PGDATA/postgresql.conf" |
| echo "max_connections = 100" >> "$PGDATA/postgresql.conf" |
| echo "shared_buffers = 128MB" >> "$PGDATA/postgresql.conf" |
| echo "work_mem = 16MB" >> "$PGDATA/postgresql.conf" |
| echo "maintenance_work_mem = 128MB" >> "$PGDATA/postgresql.conf" |
| echo "effective_cache_size = 512MB" >> "$PGDATA/postgresql.conf" |
| fi |
|
|
| |
| echo "Starting PostgreSQL server..." |
| pg_ctl start -D "$PGDATA" -l /var/log/postgresql/postgresql.log -o "-c logging_collector=on -c log_directory='/var/log/postgresql' -c log_filename='postgresql-%Y-%m-%d_%H%M%S.log' -c log_statement='all'" -w |
|
|
| |
| max_tries=30 |
| count=0 |
| echo "Checking database connection..." |
| until pg_isready -h localhost -p 5432; do |
| if [ $count -eq 0 ]; then |
| echo "PostgreSQL logs:" |
| tail -n 50 /var/log/postgresql/postgresql.log |
| fi |
| echo "Waiting for database connection... (${count}/${max_tries})" |
| sleep 2 |
| count=$((count+1)) |
| if [ $count -gt $max_tries ]; then |
| echo "Failed to connect to database after ${max_tries} attempts" |
| echo "Last 100 lines of PostgreSQL logs:" |
| tail -n 100 /var/log/postgresql/postgresql.log |
| exit 1 |
| fi |
| done |
|
|
| |
| if ! psql -lqt | cut -d \| -f 1 | grep -qw dify; then |
| echo "Creating database dify..." |
| createdb -U user dify |
| fi |
|
|
| echo "Database connection successful" |
|
|
| |
| cd /app/api && poetry run python -m flask db upgrade |
|
|
| |
| echo "Starting API server..." |
| cd /app/api && poetry run python -m gunicorn app:app \ |
| --bind ${DIFY_BIND_ADDRESS:-127.0.0.1}:${DIFY_PORT:-5001} \ |
| --worker-class gevent \ |
| --workers 1 \ |
| --timeout 300 \ |
| --preload \ |
| --access-logfile - \ |
| --error-logfile - & |
|
|
| |
| echo "Waiting for API server to be ready..." |
| wait_for_port 5001 "API" 30 2 "127.0.0.1" |
|
|
| |
| echo "Starting frontend server..." |
| start_frontend |
|
|
| |
| CONTAINER_IP=$(hostname -i || ip route get 1 | awk '{print $7}' || echo "127.0.0.1") |
| echo "Container IP: $CONTAINER_IP" |
|
|
| |
| echo "Waiting for frontend server to be ready..." |
| if ! wait_for_port 3000 "Frontend" 60 5 "0.0.0.0"; then |
| echo "Frontend server failed to start. Last 50 lines of frontend logs:" |
| tail -n 50 /app/web/output.log |
| exit 1 |
| fi |
|
|
| |
| echo "Starting nginx..." |
| nginx -g "daemon off; error_log /var/log/nginx/error.log debug;" & |
|
|
| |
| wait_for_port 7860 "Nginx" 30 2 "0.0.0.0" |
|
|
| echo "All services are running. Starting monitoring..." |
|
|
| |
| monitor_services |
| EOT |
|
|
| |
| RUN chmod +x /app/entrypoint.sh && \ |
| chown user:user /app/entrypoint.sh |
|
|
| |
| USER user |
|
|
| |
| ENV HOME=/home/user \ |
| PATH=/usr/lib/postgresql/15/bin:/home/user/.local/bin:$PATH \ |
| PGDATA=/var/lib/postgresql/data |
|
|
| |
| FROM langgenius/dify-web:latest AS web |
| FROM langgenius/dify-api:latest AS api |
|
|
| |
| FROM base |
|
|
| |
| WORKDIR /app |
| RUN mkdir -p api web /data/storage |
|
|
| |
| COPY --from=web --chown=user:user /app/web /app/web/ |
| COPY --from=api --chown=user:user /app/api /app/api/ |
|
|
| |
| WORKDIR /app/api |
| COPY --from=api --chown=user /app/api/pyproject.toml /app/api/poetry.lock /app/api/poetry.toml ./ |
| RUN poetry install --no-root --no-dev |
|
|
| |
| RUN ln -s /data/storage /app/api/storage |
|
|
| |
| ENV FLASK_APP=app.py \ |
| EDITION=SELF_HOSTED \ |
| DEPLOY_ENV=PRODUCTION \ |
| MODE=api \ |
| LOG_LEVEL=INFO \ |
| DEBUG=false \ |
| FLASK_DEBUG=false \ |
| SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U \ |
| CONSOLE_API_URL=https://${SPACE_ID}.hf.space \ |
| CONSOLE_WEB_URL=https://${SPACE_ID}.hf.space \ |
| SERVICE_API_URL=https://${SPACE_ID}.hf.space \ |
| APP_WEB_URL=https://${SPACE_ID}.hf.space \ |
| DIFY_PORT=5001 \ |
| DIFY_BIND_ADDRESS=127.0.0.1 \ |
| DB_USERNAME=user \ |
| DB_PASSWORD=difyai123456 \ |
| DB_HOST=localhost \ |
| DB_PORT=5432 \ |
| DB_DATABASE=dify \ |
| PYTHONPATH=/app/api \ |
| STORAGE_PATH=/data/storage |
|
|
| EXPOSE 7860 |
|
|
| WORKDIR /app |
|
|
| CMD ["./entrypoint.sh"] |