Spaces:
Sleeping
Sleeping
File size: 10,883 Bytes
018ab56 | 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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 | # Deployment Guide
This document covers all deployment options for the Visual Notebook, including Docker builds, Docker Compose for development, CI/CD pipelines, Hugging Face Spaces deployment, environment configuration, and production considerations.
## Docker Build Options
The project provides three Dockerfile configurations for different deployment scenarios:
### Combined Build (Dockerfile)
The combined Dockerfile builds both the frontend and backend into a single image. This is the recommended approach for most deployments, including HF Spaces.
**Build stages**:
1. **frontend-build** (Node.js 20): Installs npm dependencies and builds the React application with `npm run build`. The output is written to `frontend/dist/`.
2. **Runtime** (Python 3.11): Installs the backend using `uv pip install --system -e .`, copies the built frontend from the previous stage into `/app/static`, and configures environment variables.
```dockerfile
# Stage 1: Build frontend
FROM node:20-slim AS frontend-build
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm install
COPY frontend/ ./
RUN npm run build
# Stage 2: Runtime
FROM python:3.11-slim
WORKDIR /app
RUN pip install uv
COPY backend/pyproject.toml backend/
COPY backend/visual_notebook/ backend/visual_notebook/
RUN cd backend && uv pip install --system -e .
COPY datasets/ /app/datasets/
COPY --from=frontend-build /app/frontend/dist /app/static
ENV DATA_DIR=/app/datasets
ENV CHECKPOINT_DIR=/app/checkpoints
ENV CORS_ORIGINS=*
ENV HOST=0.0.0.0
ENV PORT=7860
EXPOSE 7860
CMD ["uvicorn", "visual_notebook.main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
```
**Build and run**:
```bash
docker build -t visual-notebook .
docker run -p 7860:7860 -v $(pwd)/datasets:/app/datasets visual-notebook
```
### Standalone Backend (Dockerfile.backend)
The backend-only Dockerfile skips the frontend build entirely. It is intended for deployments where the frontend is served separately (e.g., by a CDN or Nginx).
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install uv
COPY backend/pyproject.toml backend/
COPY backend/visual_notebook/ backend/visual_notebook/
COPY datasets/ /app/datasets/
RUN cd backend && uv pip install --system -e .
ENV DATA_DIR=/app/datasets
ENV CHECKPOINT_DIR=/app/checkpoints
ENV CORS_ORIGINS=*
ENV HOST=0.0.0.0
ENV PORT=7860
EXPOSE 7860
CMD ["uvicorn", "visual_notebook.main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
```
### Frontend-Only (frontend/Dockerfile)
The frontend-only Dockerfile builds the React application and serves it with Nginx:
```dockerfile
FROM node:20-slim AS build
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
The Nginx configuration (`frontend/nginx.conf`) is set up to proxy API requests to the backend and serve static files for all other routes.
## Docker Compose for Development
The `docker-compose.yml` defines two services for local development:
```yaml
services:
backend:
build:
context: .
dockerfile: Dockerfile.backend
ports:
- "8000:7860"
volumes:
- ./datasets:/app/datasets
- ./checkpoints:/app/checkpoints
environment:
- DATA_DIR=/app/datasets
- CHECKPOINT_DIR=/app/checkpoints
- CORS_ORIGINS=http://localhost:5173,http://localhost:3000
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "5173:80"
environment:
- VITE_API_URL=http://localhost:8000
depends_on:
- backend
```
**Usage**:
```bash
# Start both services
docker compose up --build
# Backend available at http://localhost:8000
# Frontend available at http://localhost:5173
```
The volume mounts ensure that uploaded datasets and checkpoints persist across container restarts.
## GitHub Actions CI/CD
The recommended GitHub Actions workflow follows a test-build-deploy pipeline:
### Workflow Stages
1. **Test**: Run pytest against the backend with `uv run pytest`.
2. **Build**: Build the Docker image using the combined Dockerfile.
3. **Deploy**: Push the image to a container registry and deploy.
### Example Workflow
```yaml
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
with:
version: "latest"
- name: Install dependencies
run: uv sync
- name: Run tests
run: uv run pytest backend/visual_notebook/tests/ -v
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
```
### UV Caching
The `setup-uv` action caches the uv installation and downloaded packages. For Docker builds, the `cache-from` and `cache-to` options use GitHub Actions cache to persist Docker layers between builds, significantly reducing build times for subsequent pushes.
## GitLab CI/CD Pipeline
For GitLab deployments, the following pipeline structure is recommended:
```yaml
stages:
- test
- build
- deploy
test:
stage: test
image: python:3.11-slim
before_script:
- pip install uv
- uv sync
script:
- uv run pytest backend/visual_notebook/tests/ -v
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache curl
- curl -X POST "$DEPLOY_WEBHOOK_URL"
only:
- main
```
## Hugging Face Spaces Deployment
The combined Dockerfile is designed to be compatible with HF Spaces requirements:
- **Port 7860**: HF Spaces expects the application to listen on port 7860. The Dockerfile `EXPOSE 7860` and the uvicorn command use this port.
- **Static file serving**: The backend serves the frontend from `/app/static`, which is populated during the Docker build.
- **Environment variables**: HF Spaces allows setting secrets and environment variables through the Space settings UI.
### Deployment Steps
1. Create a new Docker Space on Hugging Face.
2. Clone the Space repository.
3. Copy the project files (or add the Space as a git remote).
4. Push to the Space repository. HF Spaces will build and deploy automatically.
### Space Configuration
Set the following secrets in the Space settings:
| Variable | Value |
|----------|-------|
| `DATA_DIR` | `/app/datasets` |
| `CHECKPOINT_DIR` | `/app/checkpoints` |
| `CORS_ORIGINS` | `*` (or your specific domain) |
| `MAX_UPLOAD_SIZE` | `524288000` (500 MB) |
### Persistent Storage
HF Spaces offers persistent storage for paid tiers. To enable it, mount a persistent volume at `/app/datasets` and `/app/checkpoints`. Without persistent storage, uploaded files are lost when the Space restarts.
## Environment Variable Configuration
All configuration is managed through environment variables, read by the `Settings` dataclass in `config.py`:
| Variable | Default | Description |
|----------|---------|-------------|
| `DATA_DIR` | `datasets` | Directory for uploaded CSV files |
| `CHECKPOINT_DIR` | `checkpoints` | Directory for session checkpoints |
| `MAX_UPLOAD_SIZE` | `524288000` (500 MB) | Maximum CSV file size in bytes |
| `PREVIEW_ROWS` | `50` | Number of rows returned by default in `head()` |
| `SAMPLE_ROWS` | `10000` | Number of rows for Polars CSV sampling |
| `CORS_ORIGINS` | `http://localhost:5173,http://localhost:3000` | Comma-separated allowed origins |
| `HOST` | `0.0.0.0` | Server bind address |
| `PORT` | `8000` | Server bind port (7860 in Docker) |
## Production Considerations
### Session Storage
The current `SessionStore` is in-memory, meaning all session data is lost when the server restarts. For production deployments, consider replacing it with a persistent store:
- **Redis**: Store session metadata in Redis with TTL-based expiration. DuckDB tables would need to be exported to Parquet and reloaded on session restore.
- **Database-backed**: Use a lightweight database (SQLite, PostgreSQL) to store session state and checkpoint references.
- **File-based**: Serialize session metadata to JSON files in the checkpoint directory.
### Static File Serving
In production, the backend serves static files from `/app/static`. For high-traffic deployments, consider:
- Using a CDN in front of the application for static asset caching.
- Serving static files directly from Nginx rather than through FastAPI's `StaticFiles` middleware.
- Enabling gzip compression for JS and CSS assets.
### Health Checks
The `/health` endpoint returns a JSON response with status and version. Configure your load balancer or orchestrator to use this endpoint for health monitoring:
```json
{"status": "ok", "version": "0.1.0"}
```
For more comprehensive health checks, consider extending the endpoint to verify DuckDB connectivity and disk space availability.
### Resource Limits
DuckDB operates entirely in memory. For datasets that exceed available RAM:
- Set `max_memory` in the DuckDB connection to enable disk spilling: `duckdb.connect(":memory:", config={"memory_limit": "2GB"})`.
- Use `PREVIEW_ROWS` and `SAMPLE_ROWS` to limit data sent to the frontend.
- Monitor memory usage and set container memory limits appropriately.
### Scaling Considerations
The current architecture is single-process and single-session-per-container. To scale horizontally:
- **Session affinity**: Use cookie-based sticky sessions to route each user to the same backend instance.
- **Shared storage**: Use a networked filesystem (EFS, S3) for `DATA_DIR` so all instances can access uploaded files.
- **Connection pooling**: If moving to a shared DuckDB instance, implement connection pooling to manage concurrent access.
- **Load balancing**: Place multiple backend instances behind a load balancer with health check routing.
For most use cases, a single instance with sufficient RAM (4-8 GB) handles dozens of concurrent users comfortably, given that CSV analysis is typically an interactive, low-concurrency workload.
|