feat: unified nginx proxy deployment
Browse files- .github/workflows/publish-ghcr.yml +8 -27
- Dockerfile +55 -0
- frontend/app.py +3 -3
- nginx.conf +48 -0
- start.sh +24 -0
.github/workflows/publish-ghcr.yml
CHANGED
|
@@ -32,40 +32,21 @@ jobs:
|
|
| 32 |
username: ${{ github.actor }}
|
| 33 |
password: ${{ secrets.GITHUB_TOKEN }}
|
| 34 |
|
| 35 |
-
#
|
| 36 |
-
- name: Extract metadata (tags, labels) for
|
| 37 |
-
id: meta-
|
| 38 |
uses: docker/metadata-action@v5
|
| 39 |
with:
|
| 40 |
-
images: ${{ env.REGISTRY }}/${{ env.REPO_LC }}-
|
| 41 |
tags: |
|
| 42 |
type=raw,value=latest
|
| 43 |
type=sha
|
| 44 |
|
| 45 |
-
- name: Build and push
|
| 46 |
uses: docker/build-push-action@v5
|
| 47 |
with:
|
| 48 |
context: .
|
| 49 |
-
file: Dockerfile
|
| 50 |
push: true
|
| 51 |
-
tags: ${{ steps.meta-
|
| 52 |
-
labels: ${{ steps.meta-
|
| 53 |
-
|
| 54 |
-
# Frontend Build & Push
|
| 55 |
-
- name: Extract metadata (tags, labels) for Frontend
|
| 56 |
-
id: meta-frontend
|
| 57 |
-
uses: docker/metadata-action@v5
|
| 58 |
-
with:
|
| 59 |
-
images: ${{ env.REGISTRY }}/${{ env.REPO_LC }}-frontend
|
| 60 |
-
tags: |
|
| 61 |
-
type=raw,value=latest
|
| 62 |
-
type=sha
|
| 63 |
-
|
| 64 |
-
- name: Build and push Frontend image
|
| 65 |
-
uses: docker/build-push-action@v5
|
| 66 |
-
with:
|
| 67 |
-
context: .
|
| 68 |
-
file: Dockerfile.frontend
|
| 69 |
-
push: true
|
| 70 |
-
tags: ${{ steps.meta-frontend.outputs.tags }}
|
| 71 |
-
labels: ${{ steps.meta-frontend.outputs.labels }}
|
|
|
|
| 32 |
username: ${{ github.actor }}
|
| 33 |
password: ${{ secrets.GITHUB_TOKEN }}
|
| 34 |
|
| 35 |
+
# Unified Build & Push
|
| 36 |
+
- name: Extract metadata (tags, labels) for Unified Image
|
| 37 |
+
id: meta-unified
|
| 38 |
uses: docker/metadata-action@v5
|
| 39 |
with:
|
| 40 |
+
images: ${{ env.REGISTRY }}/${{ env.REPO_LC }}-unified
|
| 41 |
tags: |
|
| 42 |
type=raw,value=latest
|
| 43 |
type=sha
|
| 44 |
|
| 45 |
+
- name: Build and push Unified image
|
| 46 |
uses: docker/build-push-action@v5
|
| 47 |
with:
|
| 48 |
context: .
|
| 49 |
+
file: Dockerfile
|
| 50 |
push: true
|
| 51 |
+
tags: ${{ steps.meta-unified.outputs.tags }}
|
| 52 |
+
labels: ${{ steps.meta-unified.outputs.labels }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-stage build for optimized unified (frontend + backend) deployment
|
| 2 |
+
FROM python:3.11-slim as builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# Install build dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 8 |
+
build-essential \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
# Copy and install requirements
|
| 12 |
+
COPY frontend/requirements.txt requirements.txt
|
| 13 |
+
COPY backend/requirements.txt backend_requirements.txt
|
| 14 |
+
|
| 15 |
+
RUN pip install --upgrade pip setuptools wheel && \
|
| 16 |
+
pip install --no-cache-dir -r requirements.txt && \
|
| 17 |
+
pip install --no-cache-dir -r backend_requirements.txt
|
| 18 |
+
|
| 19 |
+
# Final stage
|
| 20 |
+
FROM python:3.11-slim
|
| 21 |
+
|
| 22 |
+
WORKDIR /app
|
| 23 |
+
|
| 24 |
+
# Install runtime dependencies for executing multi-language code and Nginx for routing
|
| 25 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 26 |
+
nodejs \
|
| 27 |
+
npm \
|
| 28 |
+
g++ \
|
| 29 |
+
default-jdk \
|
| 30 |
+
dnsutils \
|
| 31 |
+
curl \
|
| 32 |
+
nginx \
|
| 33 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 34 |
+
|
| 35 |
+
# Copy Python packages from builder
|
| 36 |
+
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
| 37 |
+
COPY --from=builder /usr/local/bin /usr/local/bin
|
| 38 |
+
|
| 39 |
+
# Copy application files
|
| 40 |
+
COPY frontend/ frontend/
|
| 41 |
+
COPY backend/ backend/
|
| 42 |
+
COPY start.sh start.sh
|
| 43 |
+
COPY nginx.conf /etc/nginx/nginx.conf
|
| 44 |
+
|
| 45 |
+
# Make start script executable
|
| 46 |
+
RUN chmod +x start.sh
|
| 47 |
+
|
| 48 |
+
# Expose the single proxy port (7860 for Hugging Face or Railway dynamic PORT)
|
| 49 |
+
EXPOSE 7860
|
| 50 |
+
|
| 51 |
+
# Proxy Configuration
|
| 52 |
+
ENV PORT=7860
|
| 53 |
+
|
| 54 |
+
# Run both Streamlit app and FastAPI using the wrapper script
|
| 55 |
+
CMD ["./start.sh"]
|
frontend/app.py
CHANGED
|
@@ -123,10 +123,10 @@ for (let num of numbers) {
|
|
| 123 |
# ============================================================================
|
| 124 |
|
| 125 |
# The URL used by Streamlit (server) to contact FastAPI (server)
|
| 126 |
-
BACKEND_URL = os.getenv("BACKEND_URL", "
|
| 127 |
|
| 128 |
-
# The URL used by the browser (client JS) to contact
|
| 129 |
-
PUBLIC_BACKEND_URL = os.getenv("PUBLIC_BACKEND_URL", "
|
| 130 |
|
| 131 |
|
| 132 |
# ============================================================================
|
|
|
|
| 123 |
# ============================================================================
|
| 124 |
|
| 125 |
# The URL used by Streamlit (server) to contact FastAPI (server)
|
| 126 |
+
BACKEND_URL = os.getenv("BACKEND_URL", "http://127.0.0.1:8000")
|
| 127 |
|
| 128 |
+
# The URL used by the browser (client JS) to contact the unified Nginx proxy
|
| 129 |
+
PUBLIC_BACKEND_URL = os.getenv("PUBLIC_BACKEND_URL", "")
|
| 130 |
|
| 131 |
|
| 132 |
# ============================================================================
|
nginx.conf
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker_processes 1;
|
| 2 |
+
|
| 3 |
+
events {
|
| 4 |
+
worker_connections 1024;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
http {
|
| 8 |
+
include mime.types;
|
| 9 |
+
default_type application/octet-stream;
|
| 10 |
+
|
| 11 |
+
# Increase maximum upload size if needed (e.g. for student code or large bodies)
|
| 12 |
+
client_max_body_size 50M;
|
| 13 |
+
|
| 14 |
+
server {
|
| 15 |
+
# This port will be replaced dynamically in start.sh
|
| 16 |
+
listen 7860;
|
| 17 |
+
server_name _;
|
| 18 |
+
|
| 19 |
+
# Route API to FastAPI
|
| 20 |
+
location /api/ {
|
| 21 |
+
proxy_pass http://127.0.0.1:8000/api/;
|
| 22 |
+
proxy_set_header Host $host;
|
| 23 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 24 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
# WebSocket support for Streamlit
|
| 28 |
+
location /_stcore/stream {
|
| 29 |
+
proxy_pass http://127.0.0.1:8501/_stcore/stream;
|
| 30 |
+
proxy_http_version 1.1;
|
| 31 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 32 |
+
proxy_set_header Host $host;
|
| 33 |
+
proxy_set_header Upgrade $http_upgrade;
|
| 34 |
+
proxy_set_header Connection "upgrade";
|
| 35 |
+
proxy_read_timeout 86400;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
# Route remaining to Streamlit
|
| 39 |
+
location / {
|
| 40 |
+
proxy_pass http://127.0.0.1:8501/;
|
| 41 |
+
proxy_set_header Host $host;
|
| 42 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 43 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 44 |
+
proxy_set_header Upgrade $http_upgrade;
|
| 45 |
+
proxy_set_header Connection "upgrade";
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
}
|
start.sh
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Update Nginx listen port based on environment variable (default 7860 for HF Spaces)
|
| 4 |
+
sed -i "s/listen 7860;/listen ${PORT:-7860};/g" /etc/nginx/nginx.conf
|
| 5 |
+
|
| 6 |
+
# Start the FastAPI backend in the background on localhost
|
| 7 |
+
cd /app/backend
|
| 8 |
+
uvicorn main:app --host 127.0.0.1 --port 8000 &
|
| 9 |
+
|
| 10 |
+
# Go back to /app
|
| 11 |
+
cd /app
|
| 12 |
+
|
| 13 |
+
# Start the Streamlit frontend in the background on localhost
|
| 14 |
+
streamlit run frontend/app.py \
|
| 15 |
+
--server.port=8501 \
|
| 16 |
+
--server.address=127.0.0.1 \
|
| 17 |
+
--server.headless=true \
|
| 18 |
+
--server.enableCORS=false \
|
| 19 |
+
--server.enableXsrfProtection=false \
|
| 20 |
+
--server.enableWebsocketCompression=false &
|
| 21 |
+
|
| 22 |
+
# Start Nginx in the foreground to keep the container alive
|
| 23 |
+
echo "Starting Nginx Proxy on port ${PORT:-7860}..."
|
| 24 |
+
nginx -g "daemon off;"
|