Consolidate to single container, remove nginx

- Create unified Dockerfile with multi-stage build (Node + Python)
- FastAPI now serves React static files directly
- Remove frontend service and nginx dependency
- Simplify docker-compose to single service
- All routes work without proxy configuration
This commit is contained in:
Claude
2026-01-25 23:40:51 +00:00
parent a59887f8c5
commit 2a5b50ee4c
3 changed files with 116 additions and 31 deletions
+63
View File
@@ -0,0 +1,63 @@
# ==============================================================================
# AI Photo Edit - Unified Container
# Builds React frontend and serves it alongside FastAPI backend
# ==============================================================================
# Stage 1: Build React frontend
FROM node:20-alpine AS frontend-build
WORKDIR /frontend
# Copy package files and install dependencies
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm install
# Copy frontend source and build
COPY frontend/ ./
RUN npm run build
# Stage 2: Python backend with frontend static files
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies for OpenCV, rembg, SAM, and image processing
RUN apt-get update && apt-get install -y \
libgl1 \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev \
libgomp1 \
wget \
git \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Pre-download rembg model (u2net) to avoid first-run delay
RUN python -c "from rembg import remove; print('rembg model downloaded')" || true
# Copy backend application
COPY backend/ .
# Copy entrypoint script
COPY backend/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Copy scripts
COPY scripts/ /scripts/
# Copy built frontend from stage 1
COPY --from=frontend-build /frontend/dist /app/static
# Create data directories
RUN mkdir -p /app/data/projects /app/data/patches /app/data/models
# Expose port
EXPOSE 8000
# Use entrypoint script
ENTRYPOINT ["/entrypoint.sh"]
+48 -4
View File
@@ -1,7 +1,10 @@
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, HTMLResponse
from contextlib import asynccontextmanager
from pathlib import Path
import os
from app.config import settings
from app.database import init_db
@@ -40,9 +43,9 @@ app.include_router(generate.router)
app.include_router(tools.router)
@app.get("/")
def root():
"""API root endpoint"""
@app.get("/api")
def api_root():
"""API info endpoint"""
return {
"name": "AI Photo Edit API",
"version": "1.0.0",
@@ -54,3 +57,44 @@ def root():
def health():
"""Health check endpoint"""
return {"status": "healthy"}
# Static files directory
STATIC_DIR = Path("/app/static")
# Serve static assets (JS, CSS, images)
if STATIC_DIR.exists():
app.mount("/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="assets")
@app.get("/", response_class=HTMLResponse)
async def serve_spa():
"""Serve React SPA index.html"""
index_path = STATIC_DIR / "index.html"
if index_path.exists():
return FileResponse(index_path)
return HTMLResponse("<h1>Frontend not built. Run npm build in frontend/</h1>")
@app.get("/{full_path:path}")
async def serve_spa_routes(request: Request, full_path: str):
"""
Catch-all route for React Router (SPA).
Serves static files if they exist, otherwise returns index.html.
"""
# Don't catch API routes
if full_path.startswith(("projects", "edits", "patches", "tools", "generate", "health", "docs", "openapi.json", "api")):
return {"detail": "Not Found"}
# Check if it's a static file
static_file = STATIC_DIR / full_path
if static_file.exists() and static_file.is_file():
return FileResponse(static_file)
# Otherwise serve index.html for React Router
index_path = STATIC_DIR / "index.html"
if index_path.exists():
return FileResponse(index_path)
return HTMLResponse("<h1>Frontend not built</h1>", status_code=404)
+5 -27
View File
@@ -1,16 +1,13 @@
version: '3.8'
services:
backend:
app:
build:
context: ./backend
context: .
dockerfile: Dockerfile
container_name: ai-photo-edit-backend
container_name: ai-photo-edit
ports:
- "8101:8000" # External 8101, internal 8000
- "3080:8000"
volumes:
- ./data:/app/data
- ./backend:/app
- ./scripts:/scripts
environment:
- DATABASE_URL=sqlite:///./data/ai_photo_edit.db
@@ -19,27 +16,8 @@ services:
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- STABILITY_API_KEY=${STABILITY_API_KEY:-}
- REPLICATE_API_KEY=${REPLICATE_API_KEY:-}
- CORS_ORIGINS=http://localhost:5173,http://localhost:3000,http://localhost
- CORS_ORIGINS=*
restart: unless-stopped
networks:
- ai-photo-edit-network
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: ai-photo-edit-frontend
ports:
- "3080:80" # Frontend on port 3080
depends_on:
- backend
restart: unless-stopped
networks:
- ai-photo-edit-network
networks:
ai-photo-edit-network:
driver: bridge
volumes:
data: