|
|
|
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"] |