ai-engine / Dockerfile
Severian's picture
Update Dockerfile
cf4f7bd verified
# Base image
FROM python:3.12-slim-bookworm AS base
# Set shared environment variables
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
# Create user first (HF Spaces requirement)
RUN useradd -m -u 1000 user
# Install system dependencies and set up locales
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
# Configure nginx
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;
# Added buffer size configurations
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Added timeout configurations
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;
# Increased buffer sizes
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
# Set up directories and permissions
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
# Create and configure entrypoint script
COPY <<-'EOT' /app/entrypoint.sh
#!/bin/bash
set -e
echo "===== Application Startup at $(date "+%Y-%m-%d %H:%M:%S") ====="
# Function to check if a service is ready
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
}
# Function to check if a port is in use
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
# If we get here, the service failed to start
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
}
# Function to start the frontend server
start_frontend() {
cd /app/web
echo "Cleaning up any existing Node.js processes..."
# Kill any existing node processes more thoroughly
pkill -9 -f "node server.js" || true
# Wait a moment to ensure ports are released
sleep 2
# Check if port is still in use
if netstat -tulpn | grep ":3000" > /dev/null; then
echo "Port 3000 is still in use. Attempting to force cleanup..."
# Find and kill any process using port 3000
fuser -k 3000/tcp || true
sleep 2
fi
# Ensure the output log directory exists
touch output.log
echo "Starting Next.js server at $(date)" > output.log
# Configure Next.js to listen on all interfaces
export HOSTNAME="0.0.0.0"
export NODE_ENV=production
export PORT=3000
# Start the server with proper error handling
node server.js >> output.log 2>&1 &
local pid=$!
echo "Frontend server started with PID: $pid"
# Wait a bit to ensure process is stable and check its status
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
# Verify the port is actually being used by our process
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
}
# Function to monitor and restart services if needed
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"
# Force cleanup before restart
pkill -9 -f "node server.js" || true
fuser -k 3000/tcp || true
sleep 2
if start_frontend; then
echo "Frontend server restarted successfully"
# Reset counter on successful restart
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
}
# Initialize PostgreSQL database if not already initialized
if [ ! -f "$PGDATA/PG_VERSION" ]; then
echo "Initializing PostgreSQL database..."
initdb --username=user --pwfile=<(echo "$DB_PASSWORD") --auth=md5 --encoding=UTF8
# Configure PostgreSQL
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
# Start PostgreSQL with detailed logging
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
# Wait for PostgreSQL to start and show logs if there are issues
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
# Create database if it doesn't exist
if ! psql -lqt | cut -d \| -f 1 | grep -qw dify; then
echo "Creating database dify..."
createdb -U user dify
fi
echo "Database connection successful"
# Start application services
cd /app/api && poetry run python -m flask db upgrade
# Start API server
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 - &
# Wait for API to be ready
echo "Waiting for API server to be ready..."
wait_for_port 5001 "API" 30 2 "127.0.0.1"
# Start frontend server
echo "Starting frontend server..."
start_frontend
# Get container IP
CONTAINER_IP=$(hostname -i || ip route get 1 | awk '{print $7}' || echo "127.0.0.1")
echo "Container IP: $CONTAINER_IP"
# Wait for frontend to be ready
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
# Start nginx with debug logging
echo "Starting nginx..."
nginx -g "daemon off; error_log /var/log/nginx/error.log debug;" &
# Wait for nginx to be ready
wait_for_port 7860 "Nginx" 30 2 "0.0.0.0"
echo "All services are running. Starting monitoring..."
# Start monitoring services
monitor_services
EOT
# Set permissions for entrypoint script
RUN chmod +x /app/entrypoint.sh && \
chown user:user /app/entrypoint.sh
# Switch to user for remaining operations
USER user
# Set environment for user
ENV HOME=/home/user \
PATH=/usr/lib/postgresql/15/bin:/home/user/.local/bin:$PATH \
PGDATA=/var/lib/postgresql/data
# Pull official images
FROM langgenius/dify-web:latest AS web
FROM langgenius/dify-api:latest AS api
# Final stage
FROM base
# Set up directory structure
WORKDIR /app
RUN mkdir -p api web /data/storage
# Copy from official images
COPY --from=web --chown=user:user /app/web /app/web/
COPY --from=api --chown=user:user /app/api /app/api/
# Install API dependencies using Poetry
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
# Create symlink for persistent storage
RUN ln -s /data/storage /app/api/storage
# Set environment variables
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"]