diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..0fb043dc
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,27 @@
+.git
+.gitignore
+.gitattributes
+
+*.pyc
+.DS_Store
+Thumbs.db
+.idea/
+.vscode/
+__pycache__/
+.venv/
+env/
+venv/
+.uv-cache
+.env
+.env.*
+
+node_modules/
+WareHouse/
+data/
+temp/
+logs
+
+/frontend
+README*
+compose.yml
+Dockerfile
diff --git a/.env.docker b/.env.docker
new file mode 100644
index 00000000..104aed31
--- /dev/null
+++ b/.env.docker
@@ -0,0 +1,10 @@
+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
+
+# Explicit CORS origins for Docker-based dev (comma-separated)
+CORS_ALLOW_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
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..5982194d
--- /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 ./
+# reproducible builds:
+COPY uv.lock ./
+
+# Create the project virtualenv and install deps
+RUN uv sync --no-cache --frozen
+
+# ---- 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..d394d32b 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` 文件。
@@ -283,6 +302,7 @@ if result.final_message:
 kilo2127 |
 AckerlyLau |
 LaansDole |
+  zivkovicp |
## 🤝 致谢
diff --git a/README.md b/README.md
index f9329855..58767c4b 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.
---
@@ -290,6 +307,7 @@ By contributing to DevAll, you'll be recognized in our **Contributors** list bel
 kilo2127 |
 AckerlyLau |
 LaansDole |
+  zivkovicp |
## 🤝 Acknowledgments
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..640b6c55
--- /dev/null
+++ b/frontend/.dockerignore
@@ -0,0 +1,24 @@
+.git
+.gitignore
+.gitattributes
+
+*.pyc
+.DS_Store
+Thumbs.db
+.idea/
+.vscode/
+__pycache__/
+.venv/
+env/
+venv/
+.uv-cache
+.env
+.env.*
+
+node_modules/
+temp/
+logs
+
+README*
+compose.yml
+Dockerfile
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 00000000..c0d4804c
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,15 @@
+# ---- 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"]
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
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/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 63012cac..90d4293c 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,11 +87,42 @@ async def rate_limit_middleware(request: Request, call_next: Callable):
return response
-def add_middleware(app):
+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",
+ ]
+ 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+)?$"
+
+ 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,
+ )
+
+
+def add_middleware(app: FastAPI):
"""Add all middleware to the FastAPI application."""
- # Add middleware in the appropriate order
+ # 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