File size: 5,642 Bytes
bd0c393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
687c673
bd0c393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
687c673
 
bd0c393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
138
139
140
141
142
143
144
145
146
147
148
149
# ==============================================================================
# Open Notebook - HuggingFace Spaces Dockerfile
# Based on Dockerfile.single, adapted for HuggingFace Spaces constraints:
#   - Single container with supervisord
#   - Nginx reverse proxy on port 7860 (HF mandatory port)
#   - Runs as UID 1000 (HF mandatory user)
#   - Persistent storage at /data/
# ==============================================================================

# Stage 1: Frontend Builder
FROM node:20-slim AS frontend-builder
WORKDIR /app/frontend

# Copy dependency files first to leverage cache
COPY frontend/package.json frontend/package-lock.json ./
ARG NPM_REGISTRY=https://registry.npmjs.org/
RUN npm config set registry ${NPM_REGISTRY} \
 && npm config set fetch-retries 5 \
 && npm config set fetch-retry-mintimeout 20000 \
 && npm config set fetch-retry-maxtimeout 120000
# Retry npm ci to survive transient registry ECONNRESETs
RUN i=0; until npm ci; do \
      i=$((i+1)); \
      if [ "$i" -ge 5 ]; then echo "npm ci failed after $i attempts"; exit 1; fi; \
      echo "npm ci failed (attempt $i); retrying in 15s"; sleep 15; \
    done

# Copy the rest of the frontend source
COPY frontend/ ./
# Build the frontend
RUN npm run build

# Stage 2: SurrealDB binary (pinned to v2 to match docker-compose.yml)
FROM surrealdb/surrealdb:v2 AS surreal-binary

# Stage 3: Backend Builder
FROM python:3.12-slim-bookworm AS backend-builder
# Install build dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/*
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app

# Set build optimization environment variables
ENV UV_HTTP_TIMEOUT=120

# Copy dependency files first
COPY pyproject.toml uv.lock ./
COPY open_notebook/__init__.py ./open_notebook/__init__.py
# Install dependencies
RUN uv sync --frozen --no-dev

# Pre-download tiktoken encoding so the app works offline (issue #264).
ENV TIKTOKEN_CACHE_DIR=/app/tiktoken-cache
RUN mkdir -p /app/tiktoken-cache && \
    .venv/bin/python -c "import tiktoken; tiktoken.get_encoding('o200k_base')"

# ==============================================================================
# Stage 4: Runtime (HuggingFace Spaces optimized)
# ==============================================================================
FROM python:3.12-slim-bookworm AS runtime

# Install runtime dependencies (including nginx for reverse proxy)
# All apt installs MUST happen before switching to non-root user
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
    ffmpeg \
    supervisor \
    nginx \
    curl \
    && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs \
    && rm -rf /var/lib/apt/lists/*

# Install SurrealDB binary (from pinned v2 image)
COPY --from=surreal-binary /surreal /usr/local/bin/surreal

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# ==============================================================================
# HuggingFace Spaces: Create user with UID 1000 (mandatory)
# ==============================================================================
RUN useradd -m -u 1000 user

WORKDIR /app

# Copy backend virtualenv and source code
COPY --from=backend-builder /app/.venv /app/.venv
COPY . /app/

# Copy pre-downloaded tiktoken encoding from builder
COPY --from=backend-builder /app/tiktoken-cache /app/tiktoken-cache

# Copy built frontend from standalone output
COPY --from=frontend-builder /app/frontend/.next/standalone /app/frontend/
COPY --from=frontend-builder /app/frontend/.next/static /app/frontend/.next/static
COPY --from=frontend-builder /app/frontend/public /app/frontend/public

# Bind Next.js to all interfaces
ENV HOSTNAME=0.0.0.0
# Point the app at the pre-baked tiktoken encoding
ENV TIKTOKEN_CACHE_DIR=/app/tiktoken-cache

# ==============================================================================
# Setup directories, Nginx, and permissions for UID 1000
# ==============================================================================

# Copy HuggingFace-specific configuration files
COPY nginx.hf.conf /etc/nginx/nginx.conf
COPY supervisord.hf.conf /etc/supervisor/conf.d/supervisord.conf
COPY start.sh /app/start.sh
RUN chmod +x /app/start.sh

# Ensure wait-for-api script is executable
RUN chmod +x /app/scripts/wait-for-api.sh

# Setup directories with proper permissions for UID 1000
RUN mkdir -p /app/data /data /var/log/supervisor /var/log/nginx \
    /var/lib/nginx /var/lib/nginx/body /var/lib/nginx/proxy \
    /var/lib/nginx/fastcgi /var/lib/nginx/uwsgi /var/lib/nginx/scgi \
    /run /tmp/nginx

# Set ownership for non-root user (UID 1000)
RUN chown -R 1000:1000 /app /data /var/log/supervisor /var/log/nginx \
    /var/lib/nginx /run /tmp/nginx /etc/nginx && \
    chown -R 1000:1000 /var/run 2>/dev/null || true

# Remove default nginx configs that might conflict
RUN rm -rf /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/conf.d

# ==============================================================================
# Environment defaults for HuggingFace
# ==============================================================================
ENV SURREAL_URL=ws://localhost:8000/rpc
ENV SURREAL_USER=root
ENV SURREAL_PASSWORD=root
ENV SURREAL_NAMESPACE=open_notebook
ENV SURREAL_DATABASE=open_notebook
ENV OPEN_NOTEBOOK_ENCRYPTION_KEY=change-me-in-hf-secrets

# Switch to non-root user (HF Spaces requirement)
USER user

# Expose HuggingFace's mandatory port
EXPOSE 7860

# Start via our initialization script
CMD ["/app/start.sh"]