From d60bb77bf05c8d29e5c9cdd8728ff60c94ea7059 Mon Sep 17 00:00:00 2001 From: Petar Zivkovic Date: Fri, 6 Feb 2026 22:54:28 +0100 Subject: [PATCH 1/5] add: Docker support with multi-stage build and Compose --- .dockerignore | 28 +++++++++++++++++++ .env.docker | 7 +++++ .gitignore | 2 +- Dockerfile | 63 ++++++++++++++++++++++++++++++++++++++++++ README-zh.md | 21 +++++++++++++- README.md | 19 ++++++++++++- compose.yml | 33 ++++++++++++++++++++++ frontend/.dockerignore | 25 +++++++++++++++++ frontend/.gitignore | 2 +- frontend/Dockerfile | 33 ++++++++++++++++++++++ 10 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.docker create mode 100644 Dockerfile create mode 100644 compose.yml create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..027657ff --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +.git +.gitignore +.gitattributes + +*.pyc +.DS_Store +Thumbs.db +.idea/ +.vscode/ +__pycache__/ +.venv/ +env/ +venv/ +.uv-cache +.env +.env.* + +node_modules/ +WareHouse/ +data/ +temp/ +logs +.aider* + +/frontend +README* +compose.yml +Dockerfile diff --git a/.env.docker b/.env.docker new file mode 100644 index 00000000..1f49c7e2 --- /dev/null +++ b/.env.docker @@ -0,0 +1,7 @@ +BACKEND_BIND=0.0.0.0 + +FRONTEND_HOST=0.0.0.0 +FRONTEND_PORT=5173 + +# Frontend points to the backend service name and exposed port +VITE_API_BASE_URL=http://backend:6400 diff --git a/.gitignore b/.gitignore index 96b6481f..ca4ef755 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ logs/ node_modules/ data/ temp/ -WareHouse/ \ No newline at end of file +WareHouse/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..954d39c9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +# ---- Builder: install deps with compilers and uv ---- +FROM python:3.12-slim AS builder +ARG DEBIAN_FRONTEND=noninteractive + +WORKDIR /app + +# System deps required to build Python packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config \ + build-essential \ + python3-dev \ + libcairo2-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install uv just for dependency resolution/install +RUN pip install --no-cache-dir uv + +# Install the project virtualenv outside /app so bind-mounts don't hide it +ENV UV_PROJECT_ENVIRONMENT=/opt/venv + +# Copy dependency files first to maximize cache +COPY pyproject.toml ./ +# If you have a lock file, uncomment the next line for reproducible builds: +# COPY uv.lock ./ + +# Create the project virtualenv and install deps +RUN uv sync --no-cache + +# ---- Runtime: minimal image with only runtime libs + app ---- +FROM python:3.12-slim AS runtime +ARG DEBIAN_FRONTEND=noninteractive +ARG BACKEND_BIND=0.0.0.0 + +WORKDIR /app + +# Install only runtime system libraries (no compilers) +# Keep libcairo if your deps need it; remove if unnecessary +RUN apt-get update && apt-get install -y --no-install-recommends \ + libcairo2 \ + && rm -rf /var/lib/apt/lists/* + +# Copy the prebuilt virtualenv from the builder +COPY --from=builder /opt/venv /opt/venv + +# Copy the rest of the application code +COPY . . + +# Use the venv Python by default and keep Python output unbuffered. +# Bake default bind/port into the image; can be overridden at runtime. +ENV PATH="/opt/venv/bin:${PATH}" \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + BACKEND_BIND=${BACKEND_BIND} + +# Drop privileges +RUN useradd -m appuser && chown -R appuser:appuser /app +USER appuser + +# EXPOSE is informational; compose controls published ports +EXPOSE 6400 + +# Command to run the backend server, parameterized by env +CMD ["sh", "-c", "python server_main.py --port 6400 --host ${BACKEND_BIND:-0.0.0.0}"] diff --git a/README-zh.md b/README-zh.md index 4f3dc76a..db73c288 100755 --- a/README-zh.md +++ b/README-zh.md @@ -118,7 +118,7 @@ ChatDev 已从一个专门的软件开发多智能体系统演变为一个全面 cd frontend && npm install ``` -### ⚡️ 运行应用 +### ⚡️ 运行应用(本地) #### 使用 Makefile(推荐) @@ -171,6 +171,25 @@ make dev 检查所有 YAML 文件的语法与 schema 错误。 +### 🐳 使用 Docker 运行 +你也可以通过 Docker Compose 运行整个应用。该方式可简化依赖管理,并提供一致的运行环境。 + +1. **前置条件**: + * 已安装 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/)。 + * 请确保在项目根目录中存在用于配置 API Key 的 `.env` 文件。 + +2. **构建并运行**: + ```bash + # 在项目根目录执行 + docker compose up --build + ``` + +3. **访问地址**: + * **后端**:`http://localhost:6400` + * **前端**:`http://localhost:5173` + +> 服务在异常退出后会自动重启,本地文件的修改会同步映射到容器中,便于实时开发。 + ### 🔑 配置 * **环境变量**:在项目根目录创建一个 `.env` 文件。 diff --git a/README.md b/README.md index f9329855..38669c8e 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,6 @@ See our paper in [Multi-Agent Collaboration via Evolving Orchestration](https:// ### ⚡️ Run the Application - #### Using Makefile (Recommended) **Start both Backend and Frontent**: @@ -183,6 +182,24 @@ make dev ``` Checks all YAML files for syntax and schema errors. +### 🐳 Run with Docker +Alternatively, you can run the entire application using Docker Compose. This method simplifies dependency management and provides a consistent environment. + +1. **Prerequisites**: + * [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed. + * Ensure you have a `.env` file in the project root for your API keys. + +2. **Build and Run**: + ```bash + # From the project root + docker compose up --build + ``` + +3. **Access**: + * **Backend**: `http://localhost:6400` + * **Frontend**: `http://localhost:5173` + +> The services will automatically restart if they crash, and local file changes will be reflected inside the containers for live development. --- diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..d2490116 --- /dev/null +++ b/compose.yml @@ -0,0 +1,33 @@ +services: + backend: + build: + context: . + dockerfile: Dockerfile + target: runtime + container_name: chatdev_backend + volumes: + - .:/app + ports: + - "6400:6400" + env_file: + - .env + - .env.docker + restart: unless-stopped + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + target: dev + container_name: chatdev_frontend + volumes: + - ./frontend:/app + - /app/node_modules + ports: + - "${FRONTEND_PORT:-5173}:5173" + env_file: + - .env + - .env.docker + depends_on: + - backend + restart: unless-stopped diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..8a7c39a8 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,25 @@ +.git +.gitignore +.gitattributes + +*.pyc +.DS_Store +Thumbs.db +.idea/ +.vscode/ +__pycache__/ +.venv/ +env/ +venv/ +.uv-cache +.env +.env.* + +node_modules/ +temp/ +logs +.aider* + +README* +compose.yml +Dockerfile diff --git a/frontend/.gitignore b/frontend/.gitignore index a547bf36..a0cc46da 100755 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -7,7 +7,7 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -node_modules +node_modules/ dist dist-ssr *.local diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..8204de88 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,33 @@ +# ---- Dependencies: install node_modules once (cached) ---- +FROM node:24-alpine AS deps +WORKDIR /app +COPY package*.json ./ +# Prefer reproducible installs; fall back if no lockfile +RUN npm ci --no-audit --no-fund || npm install --no-audit --no-fund + +# ---- Dev runtime: hot-reload server ---- +FROM node:24-alpine AS dev +WORKDIR /app +ENV NODE_ENV=development +COPY --from=deps /app/node_modules /app/node_modules +COPY . . +EXPOSE 5173 +CMD ["npm", "run", "dev", "--", "--host"] + +# ---- Build: create production assets ---- +FROM node:24-alpine AS build +WORKDIR /app +ENV NODE_ENV=production +ARG VITE_API_BASE_URL=http://backend:6400 +ENV VITE_API_BASE_URL="" +COPY --from=deps /app/node_modules /app/node_modules +COPY . . +RUN npm run build + +# ---- Prod runtime: serve static dist with nginx ---- +FROM nginx:alpine AS prod +# For SPA deep links you might add a custom nginx.conf with index.html fallback. +# COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] From 83a7c36f3b14e79341ebdfaa48463c0dde6f58e3 Mon Sep 17 00:00:00 2001 From: Petar Zivkovic Date: Sat, 7 Feb 2026 00:56:47 +0100 Subject: [PATCH 2/5] fix: resolve runtime issues in Docker environment --- .dockerignore | 1 - .env.docker | 3 ++ README-zh.md | 17 ++++++++++++ compose.prod.yml | 44 ++++++++++++++++++++++++++++++ frontend/.dockerignore | 1 - frontend/.gitignore | 2 +- frontend/Dockerfile | 2 +- frontend/nginx.conf | 32 ++++++++++++++++++++++ frontend/src/pages/LaunchView.vue | 22 ++++++++++++--- frontend/src/utils/apiFunctions.js | 3 +- frontend/vite.config.js | 8 +++++- utils/middleware.py | 34 +++++++++++++++++++++-- 12 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 compose.prod.yml create mode 100644 frontend/nginx.conf diff --git a/.dockerignore b/.dockerignore index 027657ff..0fb043dc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,7 +20,6 @@ WareHouse/ data/ temp/ logs -.aider* /frontend README* diff --git a/.env.docker b/.env.docker index 1f49c7e2..104aed31 100644 --- a/.env.docker +++ b/.env.docker @@ -5,3 +5,6 @@ FRONTEND_PORT=5173 # Frontend points to the backend service name and exposed port VITE_API_BASE_URL=http://backend:6400 + +# Explicit CORS origins for Docker-based dev (comma-separated) +CORS_ALLOW_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 diff --git a/README-zh.md b/README-zh.md index db73c288..4db5fd3a 100755 --- a/README-zh.md +++ b/README-zh.md @@ -190,6 +190,23 @@ make dev > 服务在异常退出后会自动重启,本地文件的修改会同步映射到容器中,便于实时开发。 +#### 生产环境(Docker + nginx) + +- 构建并运行: + ```bash + docker compose -f compose.yml -f compose.prod.yml up -d --build + ``` +- 访问: + - 前端:http://localhost:8080 + - 后端:不对外暴露,由 nginx 通过 /api 与 /ws 转发 +- 停止: + ```bash + docker compose -f compose.yml -f compose.prod.yml down + ``` +- 说明: + - .env 保存密钥(API Keys),.env.docker 保存容器相关配置。 + - nginx 将 /api 与 /ws 代理到 backend:6400(见 frontend/nginx.conf)。 + ### 🔑 配置 * **环境变量**:在项目根目录创建一个 `.env` 文件。 diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 00000000..237ebbb6 --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,44 @@ +services: + backend: + build: + context: . + dockerfile: Dockerfile + target: runtime + container_name: chatdev_backend + env_file: + - .env + - .env.docker + expose: + - "6400" + restart: unless-stopped + networks: + - appnet + # Optional healthcheck (adjust path if different) + # healthcheck: + # test: ["CMD-SHELL", "curl -fsS http://localhost:6400/api/health || exit 1"] + # interval: 10s + # timeout: 3s + # retries: 5 + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + target: prod + container_name: chatdev_frontend + ports: + - "8080:80" + depends_on: + - backend + restart: unless-stopped + networks: + - appnet + # Optional healthcheck + # healthcheck: + # test: ["CMD-SHELL", "wget -q -O - http://localhost/ >/dev/null 2>&1 || exit 1"] + # interval: 10s + # timeout: 3s + # retries: 5 + +networks: + appnet: {} diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 8a7c39a8..640b6c55 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -18,7 +18,6 @@ venv/ node_modules/ temp/ logs -.aider* README* compose.yml diff --git a/frontend/.gitignore b/frontend/.gitignore index a0cc46da..a547bf36 100755 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -7,7 +7,7 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -node_modules/ +node_modules dist dist-ssr *.local diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 8204de88..b38d24c4 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -27,7 +27,7 @@ RUN npm run build # ---- Prod runtime: serve static dist with nginx ---- FROM nginx:alpine AS prod # For SPA deep links you might add a custom nginx.conf with index.html fallback. -# COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 00000000..2138c209 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name _; + + # Serve built assets + root /usr/share/nginx/html; + index index.html; + + # Proxy API + location /api/ { + proxy_pass http://backend:6400/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + 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 WebSocket + location /ws { + proxy_pass http://backend:6400/ws; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + # SPA fallback + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/frontend/src/pages/LaunchView.vue b/frontend/src/pages/LaunchView.vue index a8683a54..d8361b53 100755 --- a/frontend/src/pages/LaunchView.vue +++ b/frontend/src/pages/LaunchView.vue @@ -1363,10 +1363,24 @@ const establishWebSocketConnection = () => { return } - const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000' - const wsProtocol = baseUrl.startsWith('https') ? 'wss:' : 'ws:' - const urlObj = new URL(baseUrl) - const wsUrl = `${wsProtocol}//${urlObj.host}/ws` + const apiBase = import.meta.env.VITE_API_BASE_URL || '' + // Defaults: same-origin (works with Vite dev proxy) + const defaultScheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + let scheme = defaultScheme + let host = window.location.host + + // In production, prefer explicit API base if provided + if (!import.meta.env.DEV && apiBase) { + try { + const api = new URL(apiBase, window.location.origin) + scheme = api.protocol === 'https:' ? 'wss:' : 'ws:' + host = api.host + } catch { + // keep defaults + } + } + + const wsUrl = `${scheme}//${host}/ws` const socket = new WebSocket(wsUrl) ws = socket diff --git a/frontend/src/utils/apiFunctions.js b/frontend/src/utils/apiFunctions.js index c1ba9a1f..47a977b9 100755 --- a/frontend/src/utils/apiFunctions.js +++ b/frontend/src/utils/apiFunctions.js @@ -1,7 +1,6 @@ import yaml from 'js-yaml' -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000' -const apiUrl = (path) => `${API_BASE_URL}${path}` +const apiUrl = (path) => path const addYamlSuffix = (filename) => { const trimmed = (filename || '').trim() diff --git a/frontend/vite.config.js b/frontend/vite.config.js index b8a9b11f..9413b917 100755 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -4,15 +4,21 @@ import vue from '@vitejs/plugin-vue' // https://vite.dev/config/ export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') - const target = env.VITE_API_BASE_URL || 'http://localhost:8000' + const target = env.VITE_API_BASE_URL || 'http://localhost:6400' return { plugins: [vue()], server: { + host: true, proxy: { '/api': { target: target, changeOrigin: true, + }, + '/ws': { + target: target, + ws: true, + changeOrigin: true, } } } diff --git a/utils/middleware.py b/utils/middleware.py index 63012cac..3cc40f18 100755 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -2,10 +2,12 @@ import uuid from typing import Callable, Awaitable -from fastapi import Request, HTTPException +from fastapi import Request, HTTPException, FastAPI from fastapi.responses import JSONResponse +from fastapi.middleware.cors import CORSMiddleware import time import re +import os from utils.structured_logger import get_server_logger, LogType from utils.exceptions import SecurityError @@ -85,9 +87,35 @@ async def rate_limit_middleware(request: Request, call_next: Callable): return response -def add_middleware(app): +def add_middleware(app: FastAPI): """Add all middleware to the FastAPI application.""" - # Add middleware in the appropriate order + # CORS (dev defaults; override via CORS_ALLOW_ORIGINS comma-separated list) + default_origins = [ + "http://localhost:5173", + "http://127.0.0.1:5173", + ] + env_origins = os.getenv("CORS_ALLOW_ORIGINS") + if env_origins: + origins = [o.strip() for o in env_origins.split(",") if o.strip()] + origin_regex = None + else: + origins = default_origins + # Helpful in dev: allow localhost/127.0.0.1 on any port + origin_regex = r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$" + + # Add CORS middleware first to handle preflight requests and allow origins. + app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_origin_regex=origin_regex, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + expose_headers=["X-Correlation-ID"], + max_age=600, + ) + + # Add other middleware app.middleware("http")(correlation_id_middleware) app.middleware("http")(security_middleware) # app.middleware("http")(rate_limit_middleware) # Enable if needed From b3a22b4fbd55d70fa39a00882268e41b0e3c7518 Mon Sep 17 00:00:00 2001 From: Petar Zivkovic Date: Mon, 9 Feb 2026 16:40:34 +0100 Subject: [PATCH 3/5] fix: fix websocket connection issue in BatchRunView --- frontend/src/pages/BatchRunView.vue | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/BatchRunView.vue b/frontend/src/pages/BatchRunView.vue index 128ccbdf..209b282b 100644 --- a/frontend/src/pages/BatchRunView.vue +++ b/frontend/src/pages/BatchRunView.vue @@ -969,10 +969,24 @@ const establishWebSocketConnection = () => { return } - const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000' - const wsProtocol = baseUrl.startsWith('https') ? 'wss:' : 'ws:' - const urlObj = new URL(baseUrl) - const wsUrl = `${wsProtocol}//${urlObj.host}/ws` + const apiBase = import.meta.env.VITE_API_BASE_URL || '' + // Defaults: same-origin (works with Vite dev proxy) + const defaultScheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + let scheme = defaultScheme + let host = window.location.host + + // In production, prefer explicit API base if provided + if (!import.meta.env.DEV && apiBase) { + try { + const api = new URL(apiBase, window.location.origin) + scheme = api.protocol === 'https:' ? 'wss:' : 'ws:' + host = api.host + } catch { + // keep defaults + } + } + + const wsUrl = `${scheme}//${host}/ws` const socket = new WebSocket(wsUrl) ws = socket From 4ee154dee3763a0e49d2e5c953ff09b2d3feba07 Mon Sep 17 00:00:00 2001 From: Petar Zivkovic Date: Mon, 9 Feb 2026 16:52:02 +0100 Subject: [PATCH 4/5] update: remove production-like settings from Docker to focus on local use --- Dockerfile | 6 +++--- README-zh.md | 17 ----------------- compose.prod.yml | 44 -------------------------------------------- frontend/Dockerfile | 18 ------------------ frontend/nginx.conf | 32 -------------------------------- 5 files changed, 3 insertions(+), 114 deletions(-) delete mode 100644 compose.prod.yml delete mode 100644 frontend/nginx.conf diff --git a/Dockerfile b/Dockerfile index 954d39c9..5982194d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,11 +20,11 @@ ENV UV_PROJECT_ENVIRONMENT=/opt/venv # Copy dependency files first to maximize cache COPY pyproject.toml ./ -# If you have a lock file, uncomment the next line for reproducible builds: -# COPY uv.lock ./ +# reproducible builds: +COPY uv.lock ./ # Create the project virtualenv and install deps -RUN uv sync --no-cache +RUN uv sync --no-cache --frozen # ---- Runtime: minimal image with only runtime libs + app ---- FROM python:3.12-slim AS runtime diff --git a/README-zh.md b/README-zh.md index 4db5fd3a..db73c288 100755 --- a/README-zh.md +++ b/README-zh.md @@ -190,23 +190,6 @@ make dev > 服务在异常退出后会自动重启,本地文件的修改会同步映射到容器中,便于实时开发。 -#### 生产环境(Docker + nginx) - -- 构建并运行: - ```bash - docker compose -f compose.yml -f compose.prod.yml up -d --build - ``` -- 访问: - - 前端:http://localhost:8080 - - 后端:不对外暴露,由 nginx 通过 /api 与 /ws 转发 -- 停止: - ```bash - docker compose -f compose.yml -f compose.prod.yml down - ``` -- 说明: - - .env 保存密钥(API Keys),.env.docker 保存容器相关配置。 - - nginx 将 /api 与 /ws 代理到 backend:6400(见 frontend/nginx.conf)。 - ### 🔑 配置 * **环境变量**:在项目根目录创建一个 `.env` 文件。 diff --git a/compose.prod.yml b/compose.prod.yml deleted file mode 100644 index 237ebbb6..00000000 --- a/compose.prod.yml +++ /dev/null @@ -1,44 +0,0 @@ -services: - backend: - build: - context: . - dockerfile: Dockerfile - target: runtime - container_name: chatdev_backend - env_file: - - .env - - .env.docker - expose: - - "6400" - restart: unless-stopped - networks: - - appnet - # Optional healthcheck (adjust path if different) - # healthcheck: - # test: ["CMD-SHELL", "curl -fsS http://localhost:6400/api/health || exit 1"] - # interval: 10s - # timeout: 3s - # retries: 5 - - frontend: - build: - context: ./frontend - dockerfile: Dockerfile - target: prod - container_name: chatdev_frontend - ports: - - "8080:80" - depends_on: - - backend - restart: unless-stopped - networks: - - appnet - # Optional healthcheck - # healthcheck: - # test: ["CMD-SHELL", "wget -q -O - http://localhost/ >/dev/null 2>&1 || exit 1"] - # interval: 10s - # timeout: 3s - # retries: 5 - -networks: - appnet: {} diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b38d24c4..c0d4804c 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -13,21 +13,3 @@ COPY --from=deps /app/node_modules /app/node_modules COPY . . EXPOSE 5173 CMD ["npm", "run", "dev", "--", "--host"] - -# ---- Build: create production assets ---- -FROM node:24-alpine AS build -WORKDIR /app -ENV NODE_ENV=production -ARG VITE_API_BASE_URL=http://backend:6400 -ENV VITE_API_BASE_URL="" -COPY --from=deps /app/node_modules /app/node_modules -COPY . . -RUN npm run build - -# ---- Prod runtime: serve static dist with nginx ---- -FROM nginx:alpine AS prod -# For SPA deep links you might add a custom nginx.conf with index.html fallback. -COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=build /app/dist /usr/share/nginx/html -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index 2138c209..00000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,32 +0,0 @@ -server { - listen 80; - server_name _; - - # Serve built assets - root /usr/share/nginx/html; - index index.html; - - # Proxy API - location /api/ { - proxy_pass http://backend:6400/api/; - proxy_http_version 1.1; - proxy_set_header Host $host; - 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 WebSocket - location /ws { - proxy_pass http://backend:6400/ws; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - } - - # SPA fallback - location / { - try_files $uri $uri/ /index.html; - } -} From 06a601359c904a77d75b81d11874390652254d27 Mon Sep 17 00:00:00 2001 From: Petar Zivkovic Date: Mon, 9 Feb 2026 16:57:50 +0100 Subject: [PATCH 5/5] fix: removed duplicate CORS middleware --- server/bootstrap.py | 9 --------- utils/middleware.py | 15 ++++++++++----- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/server/bootstrap.py b/server/bootstrap.py index 17942b85..b0f875e1 100755 --- a/server/bootstrap.py +++ b/server/bootstrap.py @@ -1,7 +1,6 @@ """Application bootstrap helpers for the FastAPI server.""" from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware from server import state from server.config_schema_router import router as config_schema_router @@ -13,14 +12,6 @@ from utils.middleware import add_middleware def init_app(app: FastAPI) -> None: """Apply shared middleware, routers, and global state to ``app``.""" - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - add_exception_handlers(app) add_middleware(app) diff --git a/utils/middleware.py b/utils/middleware.py index 3cc40f18..90d4293c 100755 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -87,9 +87,9 @@ async def rate_limit_middleware(request: Request, call_next: Callable): return response -def add_middleware(app: FastAPI): - """Add all middleware to the FastAPI application.""" - # CORS (dev defaults; override via CORS_ALLOW_ORIGINS comma-separated list) +def add_cors_middleware(app: FastAPI) -> None: + """Configure and attach CORS middleware.""" + # Dev defaults; override via CORS_ALLOW_ORIGINS (comma-separated) default_origins = [ "http://localhost:5173", "http://127.0.0.1:5173", @@ -103,7 +103,6 @@ def add_middleware(app: FastAPI): # Helpful in dev: allow localhost/127.0.0.1 on any port origin_regex = r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$" - # Add CORS middleware first to handle preflight requests and allow origins. app.add_middleware( CORSMiddleware, allow_origins=origins, @@ -115,9 +114,15 @@ def add_middleware(app: FastAPI): max_age=600, ) + +def add_middleware(app: FastAPI): + """Add all middleware to the FastAPI application.""" + # Attach CORS first to handle preflight requests and allow origins. + add_cors_middleware(app) + # Add other middleware app.middleware("http")(correlation_id_middleware) app.middleware("http")(security_middleware) # app.middleware("http")(rate_limit_middleware) # Enable if needed - return app \ No newline at end of file + return app