mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 06:12:14 +08:00
Add Kubernetes-based sandbox provider for multi-instance support (#19)
* feat: adds docker-based dev environment * docs: updates Docker command help * fix local dev * feat(sandbox): add Kubernetes-based sandbox provider for multi-instance support * fix: skills path in k8s * feat: add example config for k8s sandbox * fix: docker config * fix: load skills on docker dev * feat: support sandbox execution to Kubernetes Deployment model * chore: rename web service name
This commit is contained in:
@@ -51,3 +51,20 @@ examples/
|
|||||||
assets/
|
assets/
|
||||||
tests/
|
tests/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Exclude directories not needed in Docker context
|
||||||
|
# Frontend build only needs frontend/
|
||||||
|
# Backend build only needs backend/
|
||||||
|
scripts/
|
||||||
|
logs/
|
||||||
|
docker/
|
||||||
|
skills/
|
||||||
|
frontend/.next
|
||||||
|
frontend/node_modules
|
||||||
|
backend/.venv
|
||||||
|
backend/htmlcov
|
||||||
|
backend/.coverage
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
!frontend/README.md
|
||||||
|
!backend/README.md
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Docker provides a consistent, isolated environment with all dependencies pre-con
|
|||||||
|
|
||||||
3. **Start development services**:
|
3. **Start development services**:
|
||||||
```bash
|
```bash
|
||||||
make docker-dev
|
make docker-start
|
||||||
```
|
```
|
||||||
All services will start with hot-reload enabled:
|
All services will start with hot-reload enabled:
|
||||||
- Frontend changes are automatically reloaded
|
- Frontend changes are automatically reloaded
|
||||||
@@ -59,20 +59,16 @@ Docker provides a consistent, isolated environment with all dependencies pre-con
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View all logs
|
# View all logs
|
||||||
make docker-dev-logs
|
make docker-logs
|
||||||
|
|
||||||
# View specific service logs
|
|
||||||
./scripts/docker.sh logs --web # Frontend only
|
|
||||||
./scripts/docker.sh logs --api # Backend only
|
|
||||||
|
|
||||||
# Restart services
|
# Restart services
|
||||||
./scripts/docker.sh restart
|
make docker-restart
|
||||||
|
|
||||||
# Stop services
|
# Stop services
|
||||||
make docker-dev-stop
|
make docker-stop
|
||||||
|
|
||||||
# Get help
|
# Get help
|
||||||
./scripts/docker.sh help
|
make docker-help
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Docker Architecture
|
#### Docker Architecture
|
||||||
|
|||||||
14
Makefile
14
Makefile
@@ -1,6 +1,6 @@
|
|||||||
# DeerFlow - Unified Development Environment
|
# DeerFlow - Unified Development Environment
|
||||||
|
|
||||||
.PHONY: help check install dev stop clean docker-init docker-start docker-stop docker-logs docker-logs-web docker-logs-api
|
.PHONY: help check install dev stop clean docker-init docker-start docker-stop docker-logs docker-logs-frontend docker-logs-gateway
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "DeerFlow Development Commands:"
|
@echo "DeerFlow Development Commands:"
|
||||||
@@ -16,8 +16,8 @@ help:
|
|||||||
@echo " make docker-start - Start all services in Docker (localhost:2026)"
|
@echo " make docker-start - Start all services in Docker (localhost:2026)"
|
||||||
@echo " make docker-stop - Stop Docker development services"
|
@echo " make docker-stop - Stop Docker development services"
|
||||||
@echo " make docker-logs - View Docker development logs"
|
@echo " make docker-logs - View Docker development logs"
|
||||||
@echo " make docker-logs-web - View Docker frontend logs"
|
@echo " make docker-logs-frontend - View Docker frontend logs"
|
||||||
@echo " make docker-logs-api - View Docker backend logs"
|
@echo " make docker-logs-gateway - View Docker gateway logs"
|
||||||
|
|
||||||
# Check required tools
|
# Check required tools
|
||||||
check:
|
check:
|
||||||
@@ -251,7 +251,7 @@ docker-logs:
|
|||||||
@./scripts/docker.sh logs
|
@./scripts/docker.sh logs
|
||||||
|
|
||||||
# View Docker development logs
|
# View Docker development logs
|
||||||
docker-logs-web:
|
docker-logs-frontend:
|
||||||
@./scripts/docker.sh logs --web
|
@./scripts/docker.sh logs --frontend
|
||||||
docker-logs-api:
|
docker-logs-gateway:
|
||||||
@./scripts/docker.sh logs --api
|
@./scripts/docker.sh logs --gateway
|
||||||
32
README.md
32
README.md
@@ -18,8 +18,7 @@ The fastest way to get started with a consistent environment:
|
|||||||
|
|
||||||
2. **Initialize and start**:
|
2. **Initialize and start**:
|
||||||
```bash
|
```bash
|
||||||
make docker-init # First time only
|
make docker-start # Start all services
|
||||||
make docker-dev # Start all services
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Access**: http://localhost:2026
|
3. **Access**: http://localhost:2026
|
||||||
@@ -56,6 +55,35 @@ If you prefer running services locally:
|
|||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed local development guide.
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed local development guide.
|
||||||
|
|
||||||
|
### Sandbox Configuration
|
||||||
|
|
||||||
|
DeerFlow supports multiple sandbox execution modes. Configure your preferred mode in `config.yaml`:
|
||||||
|
|
||||||
|
**Local Execution** (runs sandbox code directly on the host machine):
|
||||||
|
```yaml
|
||||||
|
sandbox:
|
||||||
|
use: src.sandbox.local:LocalSandboxProvider # Local execution
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker Execution** (runs sandbox code in isolated Docker containers):
|
||||||
|
```yaml
|
||||||
|
sandbox:
|
||||||
|
use: src.community.aio_sandbox:AioSandboxProvider # Docker-based sandbox
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker Execution with Kubernetes** (runs sandbox code in Kubernetes pods):
|
||||||
|
|
||||||
|
Setup Kubernetes sandbox as per [Kubernetes Sandbox Setup](docker/k8s/README.md).
|
||||||
|
```bash
|
||||||
|
./docker/k8s/setup.sh
|
||||||
|
```
|
||||||
|
Then configure `config.yaml` with the Kubernetes service URL:
|
||||||
|
```yaml
|
||||||
|
sandbox:
|
||||||
|
use: src.community.k8s_sandbox:AioSandboxProvider # Kubernetes-based sandbox
|
||||||
|
base_url: http://deer-flow-sandbox.deer-flow.svc.cluster.local:8080 # Kubernetes service URL
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 🤖 **LangGraph-based Agents** - Multi-agent orchestration with sophisticated workflows
|
- 🤖 **LangGraph-based Agents** - Multi-agent orchestration with sophisticated workflows
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ ENV PATH="/root/.local/bin:$PATH"
|
|||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy frontend source code
|
||||||
|
COPY backend ./backend
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN sh -c "cd backend && uv sync"
|
||||||
|
|
||||||
# Expose ports (gateway: 8001, langgraph: 2024)
|
# Expose ports (gateway: 8001, langgraph: 2024)
|
||||||
EXPOSE 8001 2024
|
EXPOSE 8001 2024
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://langgra.ph/schema.json",
|
"$schema": "https://langgra.ph/schema.json",
|
||||||
"dependencies": ["."],
|
"dependencies": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
"env": ".env",
|
"env": ".env",
|
||||||
"graphs": {
|
"graphs": {
|
||||||
"lead_agent": "src.agents:make_lead_agent"
|
"lead_agent": "src.agents:make_lead_agent"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ dependencies = [
|
|||||||
"dotenv>=0.9.9",
|
"dotenv>=0.9.9",
|
||||||
"fastapi>=0.115.0",
|
"fastapi>=0.115.0",
|
||||||
"httpx>=0.28.0",
|
"httpx>=0.28.0",
|
||||||
|
"kubernetes>=30.0.0",
|
||||||
"langchain>=1.2.3",
|
"langchain>=1.2.3",
|
||||||
"langchain-deepseek>=1.0.1",
|
"langchain-deepseek>=1.0.1",
|
||||||
"langchain-mcp-adapters>=0.1.0",
|
"langchain-mcp-adapters>=0.1.0",
|
||||||
@@ -30,7 +31,4 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = ["pytest>=8.0.0", "ruff>=0.14.11"]
|
||||||
"pytest>=8.0.0",
|
|
||||||
"ruff>=0.14.11",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -236,8 +236,12 @@ def _build_middlewares(config: RunnableConfig):
|
|||||||
|
|
||||||
def make_lead_agent(config: RunnableConfig):
|
def make_lead_agent(config: RunnableConfig):
|
||||||
# Lazy import to avoid circular dependency
|
# Lazy import to avoid circular dependency
|
||||||
|
import logging
|
||||||
|
|
||||||
from src.tools import get_available_tools
|
from src.tools import get_available_tools
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
thinking_enabled = config.get("configurable", {}).get("thinking_enabled", True)
|
thinking_enabled = config.get("configurable", {}).get("thinking_enabled", True)
|
||||||
model_name = config.get("configurable", {}).get("model_name") or config.get("configurable", {}).get("model")
|
model_name = config.get("configurable", {}).get("model_name") or config.get("configurable", {}).get("model")
|
||||||
is_plan_mode = config.get("configurable", {}).get("is_plan_mode", False)
|
is_plan_mode = config.get("configurable", {}).get("is_plan_mode", False)
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
from .aio_sandbox import AioSandbox
|
from .aio_sandbox import AioSandbox
|
||||||
from .aio_sandbox_provider import AioSandboxProvider
|
from .aio_sandbox_provider import AioSandboxProvider
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["AioSandbox", "AioSandboxProvider"]
|
||||||
"AioSandbox",
|
|
||||||
"AioSandboxProvider",
|
|
||||||
]
|
|
||||||
|
|||||||
64
backend/uv.lock
generated
64
backend/uv.lock
generated
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 3
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||||
@@ -606,6 +606,7 @@ dependencies = [
|
|||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "firecrawl-py" },
|
{ name = "firecrawl-py" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
|
{ name = "kubernetes" },
|
||||||
{ name = "langchain" },
|
{ name = "langchain" },
|
||||||
{ name = "langchain-deepseek" },
|
{ name = "langchain-deepseek" },
|
||||||
{ name = "langchain-mcp-adapters" },
|
{ name = "langchain-mcp-adapters" },
|
||||||
@@ -638,6 +639,7 @@ requires-dist = [
|
|||||||
{ name = "fastapi", specifier = ">=0.115.0" },
|
{ name = "fastapi", specifier = ">=0.115.0" },
|
||||||
{ name = "firecrawl-py", specifier = ">=1.15.0" },
|
{ name = "firecrawl-py", specifier = ">=1.15.0" },
|
||||||
{ name = "httpx", specifier = ">=0.28.0" },
|
{ name = "httpx", specifier = ">=0.28.0" },
|
||||||
|
{ name = "kubernetes", specifier = ">=30.0.0" },
|
||||||
{ name = "langchain", specifier = ">=1.2.3" },
|
{ name = "langchain", specifier = ">=1.2.3" },
|
||||||
{ name = "langchain-deepseek", specifier = ">=1.0.1" },
|
{ name = "langchain-deepseek", specifier = ">=1.0.1" },
|
||||||
{ name = "langchain-mcp-adapters", specifier = ">=0.1.0" },
|
{ name = "langchain-mcp-adapters", specifier = ">=0.1.0" },
|
||||||
@@ -691,6 +693,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
|
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "durationpy"
|
||||||
|
version = "0.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "et-xmlfile"
|
name = "et-xmlfile"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -1274,6 +1285,26 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kubernetes"
|
||||||
|
version = "35.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "durationpy" },
|
||||||
|
{ name = "python-dateutil" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "requests-oauthlib" },
|
||||||
|
{ name = "six" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
{ name = "websocket-client" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642, upload-time = "2026-01-16T01:05:27.76Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602, upload-time = "2026-01-16T01:05:25.991Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain"
|
name = "langchain"
|
||||||
version = "1.2.3"
|
version = "1.2.3"
|
||||||
@@ -1897,6 +1928,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ad/0d/eca3d962f9eef265f01a8e0d20085c6dd1f443cbffc11b6dede81fd82356/numpy-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6436cffb4f2bf26c974344439439c95e152c9a527013f26b3577be6c2ca64295", size = 10667121, upload-time = "2026-01-10T06:44:41.644Z" },
|
{ url = "https://files.pythonhosted.org/packages/ad/0d/eca3d962f9eef265f01a8e0d20085c6dd1f443cbffc11b6dede81fd82356/numpy-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6436cffb4f2bf26c974344439439c95e152c9a527013f26b3577be6c2ca64295", size = 10667121, upload-time = "2026-01-10T06:44:41.644Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oauthlib"
|
||||||
|
version = "3.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "olefile"
|
name = "olefile"
|
||||||
version = "0.47"
|
version = "0.47"
|
||||||
@@ -2841,6 +2881,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests-oauthlib"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "oauthlib" },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests-toolbelt"
|
name = "requests-toolbelt"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -3391,6 +3444,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
|
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "websocket-client"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "websockets"
|
name = "websockets"
|
||||||
version = "16.0"
|
version = "16.0"
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
#
|
#
|
||||||
# Services:
|
# Services:
|
||||||
# - nginx: Reverse proxy (port 2026)
|
# - nginx: Reverse proxy (port 2026)
|
||||||
# - web: Frontend Next.js dev server (port 3000)
|
# - frontend: Frontend Next.js dev server (port 3000)
|
||||||
# - api: Backend Gateway API (port 8001)
|
# - gateway: Backend Gateway API (port 8001)
|
||||||
# - langgraph: LangGraph server (port 2024)
|
# - langgraph: LangGraph server (port 2024)
|
||||||
#
|
#
|
||||||
# Access: http://localhost:2026
|
# Access: http://localhost:2026
|
||||||
@@ -19,29 +19,34 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- web
|
- frontend
|
||||||
- api
|
- gateway
|
||||||
- langgraph
|
- langgraph
|
||||||
networks:
|
networks:
|
||||||
- deer-flow-dev
|
- deer-flow-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# Frontend - Next.js Development Server
|
# Frontend - Next.js Development Server
|
||||||
web:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ../frontend
|
context: ../
|
||||||
dockerfile: Dockerfile
|
dockerfile: frontend/Dockerfile
|
||||||
args:
|
args:
|
||||||
PNPM_STORE_PATH: ${PNPM_STORE_PATH:-/root/.local/share/pnpm/store}
|
PNPM_STORE_PATH: ${PNPM_STORE_PATH:-/root/.local/share/pnpm/store}
|
||||||
container_name: deer-flow-web
|
container_name: deer-flow-frontend
|
||||||
command: pnpm run dev
|
command: sh -c "cd frontend && pnpm run dev > /app/logs/frontend.log 2>&1"
|
||||||
volumes:
|
volumes:
|
||||||
- ../frontend:/app
|
- ../frontend/src:/app/frontend/src
|
||||||
|
- ../frontend/public:/app/frontend/public
|
||||||
|
- ../frontend/next.config.js:/app/frontend/next.config.js:ro
|
||||||
|
- ../logs:/app/logs
|
||||||
# Mount pnpm store for caching
|
# Mount pnpm store for caching
|
||||||
- ${PNPM_STORE_PATH:-~/.local/share/pnpm/store}:/root/.local/share/pnpm/store
|
- ${PNPM_STORE_PATH:-~/.local/share/pnpm/store}:/root/.local/share/pnpm/store
|
||||||
|
working_dir: /app
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- WATCHPACK_POLLING=true
|
- WATCHPACK_POLLING=true
|
||||||
|
- CI=true
|
||||||
env_file:
|
env_file:
|
||||||
- ../frontend/.env
|
- ../frontend/.env
|
||||||
networks:
|
networks:
|
||||||
@@ -49,17 +54,26 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# Backend - Gateway API
|
# Backend - Gateway API
|
||||||
api:
|
gateway:
|
||||||
build:
|
build:
|
||||||
context: ../backend
|
context: ../
|
||||||
dockerfile: Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
container_name: deer-flow-api
|
container_name: deer-flow-gateway
|
||||||
command: uv run uvicorn src.gateway.app:app --host 0.0.0.0 --port 8001 --reload
|
command: sh -c "cd backend && uv run uvicorn src.gateway.app:app --host 0.0.0.0 --port 8001 --reload --reload-include='*.yaml .env' > /app/logs/gateway.log 2>&1"
|
||||||
volumes:
|
volumes:
|
||||||
- ../backend:/app
|
- ../backend/src:/app/backend/src
|
||||||
- ../config.yaml:/app/config.yaml:ro
|
- ../backend/.env:/app/backend/.env
|
||||||
|
- ../config.yaml:/app/config.yaml
|
||||||
|
- ../skills:/app/skills
|
||||||
|
- ../logs:/app/logs
|
||||||
|
working_dir: /app
|
||||||
|
environment:
|
||||||
|
- CI=true
|
||||||
env_file:
|
env_file:
|
||||||
- ../backend/.env
|
- ../backend/.env
|
||||||
|
extra_hosts:
|
||||||
|
# For Linux: map host.docker.internal to host gateway
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
networks:
|
networks:
|
||||||
- deer-flow-dev
|
- deer-flow-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -67,13 +81,19 @@ services:
|
|||||||
# Backend - LangGraph Server
|
# Backend - LangGraph Server
|
||||||
langgraph:
|
langgraph:
|
||||||
build:
|
build:
|
||||||
context: ../backend
|
context: ../
|
||||||
dockerfile: Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
container_name: deer-flow-langgraph
|
container_name: deer-flow-langgraph
|
||||||
command: uv run langgraph dev --no-browser --allow-blocking --no-reload --host 0.0.0.0 --port 2024
|
command: sh -c "cd backend && uv run langgraph dev --no-browser --allow-blocking --host 0.0.0.0 --port 2024 > /app/logs/langgraph.log 2>&1"
|
||||||
volumes:
|
volumes:
|
||||||
- ../backend:/app
|
- ../backend/src:/app/backend/src
|
||||||
- ../config.yaml:/app/config.yaml:ro
|
- ../backend/.env:/app/backend/.env
|
||||||
|
- ../config.yaml:/app/config.yaml
|
||||||
|
- ../skills:/app/skills
|
||||||
|
- ../logs:/app/logs
|
||||||
|
working_dir: /app
|
||||||
|
environment:
|
||||||
|
- CI=true
|
||||||
env_file:
|
env_file:
|
||||||
- ../backend/.env
|
- ../backend/.env
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
427
docker/k8s/README.md
Normal file
427
docker/k8s/README.md
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
# Kubernetes Sandbox Setup
|
||||||
|
|
||||||
|
This guide explains how to deploy and configure the DeerFlow sandbox execution environment on Kubernetes.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Kubernetes sandbox deployment allows you to run DeerFlow's code execution sandbox in a Kubernetes cluster, providing:
|
||||||
|
|
||||||
|
- **Isolated Execution**: Sandbox runs in dedicated Kubernetes pods
|
||||||
|
- **Scalability**: Easy horizontal scaling with replica configuration
|
||||||
|
- **Cluster Integration**: Seamless integration with existing Kubernetes infrastructure
|
||||||
|
- **Persistent Skills**: Skills directory mounted from host or PersistentVolume
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you begin, ensure you have:
|
||||||
|
|
||||||
|
1. **Kubernetes Cluster**: One of the following:
|
||||||
|
- Docker Desktop with Kubernetes enabled
|
||||||
|
- OrbStack with Kubernetes enabled
|
||||||
|
- Minikube
|
||||||
|
- Any production Kubernetes cluster
|
||||||
|
|
||||||
|
2. **kubectl**: Kubernetes command-line tool
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install kubectl
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
# See: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Docker**: For pulling the sandbox image (optional, but recommended)
|
||||||
|
```bash
|
||||||
|
# Verify installation
|
||||||
|
docker version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Enable Kubernetes
|
||||||
|
|
||||||
|
**Docker Desktop:**
|
||||||
|
```
|
||||||
|
Settings → Kubernetes → Enable Kubernetes → Apply & Restart
|
||||||
|
```
|
||||||
|
|
||||||
|
**OrbStack:**
|
||||||
|
```
|
||||||
|
Settings → Enable Kubernetes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minikube:**
|
||||||
|
```bash
|
||||||
|
minikube start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Run Setup Script
|
||||||
|
|
||||||
|
The easiest way to get started:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docker/k8s
|
||||||
|
./setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- ✅ Check kubectl installation and cluster connectivity
|
||||||
|
- ✅ Pull the sandbox Docker image (optional, can be skipped)
|
||||||
|
- ✅ Create the `deer-flow` namespace
|
||||||
|
- ✅ Deploy the sandbox service and deployment
|
||||||
|
- ✅ Verify the deployment is running
|
||||||
|
|
||||||
|
### 3. Configure Backend
|
||||||
|
|
||||||
|
Add the following to `backend/config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sandbox:
|
||||||
|
use: src.community.aio_sandbox:AioSandboxProvider
|
||||||
|
base_url: http://deer-flow-sandbox.deer-flow.svc.cluster.local:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Verify Deployment
|
||||||
|
|
||||||
|
Check that the sandbox pod is running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get pods -n deer-flow
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
```
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
deer-flow-sandbox-xxxxxxxxxx-xxxxx 1/1 Running 0 1m
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Custom Skills Path
|
||||||
|
|
||||||
|
By default, the setup script uses `PROJECT_ROOT/skills`. You can specify a custom path:
|
||||||
|
|
||||||
|
**Using command-line argument:**
|
||||||
|
```bash
|
||||||
|
./setup.sh --skills-path /custom/path/to/skills
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using environment variable:**
|
||||||
|
```bash
|
||||||
|
SKILLS_PATH=/custom/path/to/skills ./setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Sandbox Image
|
||||||
|
|
||||||
|
To use a different sandbox image:
|
||||||
|
|
||||||
|
**Using command-line argument:**
|
||||||
|
```bash
|
||||||
|
./setup.sh --image your-registry/sandbox:tag
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using environment variable:**
|
||||||
|
```bash
|
||||||
|
SANDBOX_IMAGE=your-registry/sandbox:tag ./setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skip Image Pull
|
||||||
|
|
||||||
|
If you already have the image locally or want to pull it manually later:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./setup.sh --skip-pull
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combined Options
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./setup.sh --skip-pull --skills-path /custom/skills --image custom/sandbox:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Deployment
|
||||||
|
|
||||||
|
If you prefer manual deployment or need more control:
|
||||||
|
|
||||||
|
### 1. Create Namespace
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f namespace.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f sandbox-service.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Deploy Sandbox
|
||||||
|
|
||||||
|
First, update the skills path in `sandbox-deployment.yaml`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Replace __SKILLS_PATH__ with your actual path
|
||||||
|
sed 's|__SKILLS_PATH__|/Users/feng/Projects/deer-flow/skills|g' \
|
||||||
|
sandbox-deployment.yaml | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually edit `sandbox-deployment.yaml` and replace `__SKILLS_PATH__` with your skills directory path.
|
||||||
|
|
||||||
|
### 4. Verify Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all resources
|
||||||
|
kubectl get all -n deer-flow
|
||||||
|
|
||||||
|
# Check pod status
|
||||||
|
kubectl get pods -n deer-flow
|
||||||
|
|
||||||
|
# Check pod logs
|
||||||
|
kubectl logs -n deer-flow -l app=deer-flow-sandbox
|
||||||
|
|
||||||
|
# Describe pod for detailed info
|
||||||
|
kubectl describe pod -n deer-flow -l app=deer-flow-sandbox
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### Resource Limits
|
||||||
|
|
||||||
|
Edit `sandbox-deployment.yaml` to adjust resource limits:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m # Minimum CPU
|
||||||
|
memory: 256Mi # Minimum memory
|
||||||
|
limits:
|
||||||
|
cpu: 1000m # Maximum CPU (1 core)
|
||||||
|
memory: 1Gi # Maximum memory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scaling
|
||||||
|
|
||||||
|
Adjust the number of replicas:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
replicas: 3 # Run 3 sandbox pods
|
||||||
|
```
|
||||||
|
|
||||||
|
Or scale dynamically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl scale deployment deer-flow-sandbox -n deer-flow --replicas=3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
The deployment includes readiness and liveness probes:
|
||||||
|
|
||||||
|
- **Readiness Probe**: Checks if the pod is ready to serve traffic
|
||||||
|
- **Liveness Probe**: Restarts the pod if it becomes unhealthy
|
||||||
|
|
||||||
|
Configure in `sandbox-deployment.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /v1/sandbox
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Pod Not Starting
|
||||||
|
|
||||||
|
Check pod status and events:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl describe pod -n deer-flow -l app=deer-flow-sandbox
|
||||||
|
```
|
||||||
|
|
||||||
|
Common issues:
|
||||||
|
- **ImagePullBackOff**: Docker image cannot be pulled
|
||||||
|
- Solution: Pre-pull image with `docker pull <image>`
|
||||||
|
- **Skills path not found**: HostPath doesn't exist
|
||||||
|
- Solution: Verify the skills path exists on the host
|
||||||
|
- **Resource constraints**: Not enough CPU/memory
|
||||||
|
- Solution: Adjust resource requests/limits
|
||||||
|
|
||||||
|
### Service Not Accessible
|
||||||
|
|
||||||
|
Verify the service is running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get service -n deer-flow
|
||||||
|
kubectl describe service deer-flow-sandbox -n deer-flow
|
||||||
|
```
|
||||||
|
|
||||||
|
Test connectivity from another pod:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl run test-pod -n deer-flow --rm -it --image=curlimages/curl -- \
|
||||||
|
curl http://deer-flow-sandbox.deer-flow.svc.cluster.local:8080/v1/sandbox
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Logs
|
||||||
|
|
||||||
|
View sandbox logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Follow logs in real-time
|
||||||
|
kubectl logs -n deer-flow -l app=deer-flow-sandbox -f
|
||||||
|
|
||||||
|
# View logs from previous container (if crashed)
|
||||||
|
kubectl logs -n deer-flow -l app=deer-flow-sandbox --previous
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check Failures
|
||||||
|
|
||||||
|
If pods show as not ready:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check readiness probe
|
||||||
|
kubectl get events -n deer-flow --sort-by='.lastTimestamp'
|
||||||
|
|
||||||
|
# Exec into pod to debug
|
||||||
|
kubectl exec -it -n deer-flow <pod-name> -- /bin/sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cleanup
|
||||||
|
|
||||||
|
### Remove All Resources
|
||||||
|
|
||||||
|
Using the setup script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./setup.sh --cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl delete -f sandbox-deployment.yaml
|
||||||
|
kubectl delete -f sandbox-service.yaml
|
||||||
|
kubectl delete namespace deer-flow
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove Specific Resources
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Delete only the deployment (keeps namespace and service)
|
||||||
|
kubectl delete deployment deer-flow-sandbox -n deer-flow
|
||||||
|
|
||||||
|
# Delete pods (they will be recreated by deployment)
|
||||||
|
kubectl delete pods -n deer-flow -l app=deer-flow-sandbox
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ DeerFlow Backend │
|
||||||
|
│ (config.yaml: base_url configured) │
|
||||||
|
└────────────────┬────────────────────────────┘
|
||||||
|
│ HTTP requests
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Kubernetes Service (ClusterIP) │
|
||||||
|
│ deer-flow-sandbox.deer-flow.svc:8080 │
|
||||||
|
└────────────────┬────────────────────────────┘
|
||||||
|
│ Load balancing
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Sandbox Pods (replicas) │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ Pod 1 │ │ Pod 2 │ │ Pod 3 │ │
|
||||||
|
│ │ Port 8080│ │ Port 8080│ │ Port 8080│ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||||
|
└────────────────┬────────────────────────────┘
|
||||||
|
│ Volume mount
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Host Skills Directory │
|
||||||
|
│ /path/to/deer-flow/skills │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Script Reference
|
||||||
|
|
||||||
|
### Command-Line Options
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./setup.sh [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Show help message
|
||||||
|
-c, --cleanup Remove all Kubernetes resources
|
||||||
|
-p, --skip-pull Skip pulling sandbox image
|
||||||
|
--image <image> Use custom sandbox image
|
||||||
|
--skills-path <path> Custom skills directory path
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
SANDBOX_IMAGE Custom sandbox image
|
||||||
|
SKILLS_PATH Custom skills path
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
./setup.sh # Use default settings
|
||||||
|
./setup.sh --skills-path /custom/path # Use custom skills path
|
||||||
|
./setup.sh --skip-pull --image custom:tag # Custom image, skip pull
|
||||||
|
SKILLS_PATH=/custom/path ./setup.sh # Use env variable
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Considerations
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
1. **Network Policies**: Restrict pod-to-pod communication
|
||||||
|
2. **RBAC**: Configure appropriate service account permissions
|
||||||
|
3. **Pod Security**: Enable pod security standards
|
||||||
|
4. **Image Security**: Scan images for vulnerabilities
|
||||||
|
|
||||||
|
### High Availability
|
||||||
|
|
||||||
|
1. **Multiple Replicas**: Run at least 3 replicas
|
||||||
|
2. **Pod Disruption Budget**: Prevent all pods from being evicted
|
||||||
|
3. **Node Affinity**: Distribute pods across nodes
|
||||||
|
4. **Resource Quotas**: Set namespace resource limits
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
|
||||||
|
1. **Prometheus**: Scrape metrics from pods
|
||||||
|
2. **Logging**: Centralized log aggregation
|
||||||
|
3. **Alerting**: Set up alerts for pod failures
|
||||||
|
4. **Tracing**: Distributed tracing for requests
|
||||||
|
|
||||||
|
### Storage
|
||||||
|
|
||||||
|
For production, consider using PersistentVolume instead of hostPath:
|
||||||
|
|
||||||
|
1. **Create PersistentVolume**: Define storage backend
|
||||||
|
2. **Create PersistentVolumeClaim**: Request storage
|
||||||
|
3. **Update Deployment**: Use PVC instead of hostPath
|
||||||
|
|
||||||
|
See `skills-pv-pvc.yaml.bak` for reference implementation.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After successful deployment:
|
||||||
|
|
||||||
|
1. **Start Backend**: `make dev` or `make docker-start`
|
||||||
|
2. **Test Sandbox**: Create a conversation and execute code
|
||||||
|
3. **Monitor**: Watch pod logs and resource usage
|
||||||
|
4. **Scale**: Adjust replicas based on workload
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues and questions:
|
||||||
|
|
||||||
|
- Check troubleshooting section above
|
||||||
|
- Review pod logs: `kubectl logs -n deer-flow -l app=deer-flow-sandbox`
|
||||||
|
- See main project documentation: [../../README.md](../../README.md)
|
||||||
|
- Report issues on GitHub
|
||||||
7
docker/k8s/namespace.yaml
Normal file
7
docker/k8s/namespace.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: deer-flow
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: deer-flow
|
||||||
|
app.kubernetes.io/component: sandbox
|
||||||
65
docker/k8s/sandbox-deployment.yaml
Normal file
65
docker/k8s/sandbox-deployment.yaml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deer-flow-sandbox
|
||||||
|
namespace: deer-flow
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: deer-flow
|
||||||
|
app.kubernetes.io/component: sandbox
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: deer-flow-sandbox
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: deer-flow-sandbox
|
||||||
|
app.kubernetes.io/name: deer-flow
|
||||||
|
app.kubernetes.io/component: sandbox
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: sandbox
|
||||||
|
image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /v1/sandbox
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /v1/sandbox
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 1Gi
|
||||||
|
volumeMounts:
|
||||||
|
- name: skills
|
||||||
|
mountPath: /mnt/skills
|
||||||
|
readOnly: true
|
||||||
|
securityContext:
|
||||||
|
privileged: false
|
||||||
|
allowPrivilegeEscalation: true
|
||||||
|
volumes:
|
||||||
|
- name: skills
|
||||||
|
hostPath:
|
||||||
|
# Path to skills directory on the host machine
|
||||||
|
# This will be replaced by setup.sh with the actual path
|
||||||
|
path: __SKILLS_PATH__
|
||||||
|
type: Directory
|
||||||
|
restartPolicy: Always
|
||||||
21
docker/k8s/sandbox-service.yaml
Normal file
21
docker/k8s/sandbox-service.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: deer-flow-sandbox
|
||||||
|
namespace: deer-flow
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: deer-flow
|
||||||
|
app.kubernetes.io/component: sandbox
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
clusterIP: None # Headless service for direct Pod DNS access
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: deer-flow-sandbox
|
||||||
|
# Enable DNS-based service discovery
|
||||||
|
# Pods will be accessible at: {pod-name}.deer-flow-sandbox.deer-flow.svc.cluster.local:8080
|
||||||
|
publishNotReadyAddresses: false
|
||||||
245
docker/k8s/setup.sh
Executable file
245
docker/k8s/setup.sh
Executable file
@@ -0,0 +1,245 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Kubernetes Sandbox Initialization Script for Deer-Flow
|
||||||
|
# This script sets up the Kubernetes environment for the sandbox provider
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
|
||||||
|
# Default sandbox image
|
||||||
|
DEFAULT_SANDBOX_IMAGE="enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${BLUE}╔════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║ Deer-Flow Kubernetes Sandbox Setup ║${NC}"
|
||||||
|
echo -e "${BLUE}╚════════════════════════════════════════════╝${NC}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Function to print status messages
|
||||||
|
info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if kubectl is installed
|
||||||
|
check_kubectl() {
|
||||||
|
info "Checking kubectl installation..."
|
||||||
|
if ! command -v kubectl &> /dev/null; then
|
||||||
|
error "kubectl is not installed. Please install kubectl first."
|
||||||
|
echo " - macOS: brew install kubectl"
|
||||||
|
echo " - Linux: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
success "kubectl is installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if Kubernetes cluster is accessible
|
||||||
|
check_cluster() {
|
||||||
|
info "Checking Kubernetes cluster connection..."
|
||||||
|
if ! kubectl cluster-info &> /dev/null; then
|
||||||
|
error "Cannot connect to Kubernetes cluster."
|
||||||
|
echo "Please ensure:"
|
||||||
|
echo " - Docker Desktop: Settings → Kubernetes → Enable Kubernetes"
|
||||||
|
echo " - Or OrbStack: Enable Kubernetes in settings"
|
||||||
|
echo " - Or Minikube: minikube start"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
success "Connected to Kubernetes cluster"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply Kubernetes resources
|
||||||
|
apply_resources() {
|
||||||
|
info "Applying Kubernetes resources..."
|
||||||
|
|
||||||
|
# Determine skills path
|
||||||
|
SKILLS_PATH="${SKILLS_PATH:-${PROJECT_ROOT}/skills}"
|
||||||
|
info "Using skills path: ${SKILLS_PATH}"
|
||||||
|
|
||||||
|
# Validate skills path exists
|
||||||
|
if [[ ! -d "${SKILLS_PATH}" ]]; then
|
||||||
|
warn "Skills path does not exist: ${SKILLS_PATH}"
|
||||||
|
warn "Creating directory..."
|
||||||
|
mkdir -p "${SKILLS_PATH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " → Creating namespace..."
|
||||||
|
kubectl apply -f "${SCRIPT_DIR}/namespace.yaml"
|
||||||
|
|
||||||
|
echo " → Creating sandbox service..."
|
||||||
|
kubectl apply -f "${SCRIPT_DIR}/sandbox-service.yaml"
|
||||||
|
|
||||||
|
echo " → Creating sandbox deployment with skills path: ${SKILLS_PATH}"
|
||||||
|
# Replace __SKILLS_PATH__ placeholder with actual path
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS
|
||||||
|
sed "s|__SKILLS_PATH__|${SKILLS_PATH}|g" "${SCRIPT_DIR}/sandbox-deployment.yaml" | kubectl apply -f -
|
||||||
|
else
|
||||||
|
# Linux
|
||||||
|
sed "s|__SKILLS_PATH__|${SKILLS_PATH}|g" "${SCRIPT_DIR}/sandbox-deployment.yaml" | kubectl apply -f -
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "All Kubernetes resources applied"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify deployment
|
||||||
|
verify_deployment() {
|
||||||
|
info "Verifying deployment..."
|
||||||
|
|
||||||
|
echo " → Checking namespace..."
|
||||||
|
kubectl get namespace deer-flow
|
||||||
|
|
||||||
|
echo " → Checking service..."
|
||||||
|
kubectl get service -n deer-flow
|
||||||
|
|
||||||
|
echo " → Checking deployment..."
|
||||||
|
kubectl get deployment -n deer-flow
|
||||||
|
|
||||||
|
echo " → Checking pods..."
|
||||||
|
kubectl get pods -n deer-flow
|
||||||
|
|
||||||
|
success "Deployment verified"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pull sandbox image
|
||||||
|
pull_image() {
|
||||||
|
info "Checking sandbox image..."
|
||||||
|
|
||||||
|
IMAGE="${SANDBOX_IMAGE:-$DEFAULT_SANDBOX_IMAGE}"
|
||||||
|
|
||||||
|
# Check if image already exists locally
|
||||||
|
if docker image inspect "$IMAGE" &> /dev/null; then
|
||||||
|
success "Image already exists locally: $IMAGE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Pulling sandbox image (this may take a few minutes on first run)..."
|
||||||
|
echo " → Image: $IMAGE"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if docker pull "$IMAGE"; then
|
||||||
|
success "Image pulled successfully"
|
||||||
|
else
|
||||||
|
warn "Failed to pull image. Pod startup may be slow on first run."
|
||||||
|
echo " You can manually pull the image later with:"
|
||||||
|
echo " docker pull $IMAGE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print next steps
|
||||||
|
print_next_steps() {
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}╔════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║ Setup Complete! ║${NC}"
|
||||||
|
echo -e "${BLUE}╚════════════════════════════════════════════╝${NC}"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}To enable Kubernetes sandbox, add the following to backend/config.yaml:${NC}"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}sandbox:${NC}"
|
||||||
|
echo -e "${GREEN} use: src.community.aio_sandbox:AioSandboxProvider${NC}"
|
||||||
|
echo -e "${GREEN} base_url: http://deer-flow-sandbox.deer-flow.svc.cluster.local:8080${NC}"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}Next steps:${NC}"
|
||||||
|
echo " make dev # Start backend and frontend in development mode"
|
||||||
|
echo " make docker-start # Start backend and frontend in Docker containers"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup function
|
||||||
|
cleanup() {
|
||||||
|
if [[ "$1" == "--cleanup" ]] || [[ "$1" == "-c" ]]; then
|
||||||
|
info "Cleaning up Kubernetes resources..."
|
||||||
|
kubectl delete -f "${SCRIPT_DIR}/sandbox-deployment.yaml" --ignore-not-found=true
|
||||||
|
kubectl delete -f "${SCRIPT_DIR}/sandbox-service.yaml" --ignore-not-found=true
|
||||||
|
kubectl delete -f "${SCRIPT_DIR}/namespace.yaml" --ignore-not-found=true
|
||||||
|
success "Cleanup complete"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show help
|
||||||
|
show_help() {
|
||||||
|
echo "Usage: $0 [options]"
|
||||||
|
echo
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo " -c, --cleanup Remove all Kubernetes resources"
|
||||||
|
echo " -p, --skip-pull Skip pulling sandbox image"
|
||||||
|
echo " --image <image> Use custom sandbox image"
|
||||||
|
echo " --skills-path <path> Custom skills directory path"
|
||||||
|
echo
|
||||||
|
echo "Environment variables:"
|
||||||
|
echo " SANDBOX_IMAGE Custom sandbox image (default: $DEFAULT_SANDBOX_IMAGE)"
|
||||||
|
echo " SKILLS_PATH Custom skills path (default: PROJECT_ROOT/skills)"
|
||||||
|
echo
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 # Use default settings"
|
||||||
|
echo " $0 --skills-path /custom/path # Use custom skills path"
|
||||||
|
echo " SKILLS_PATH=/custom/path $0 # Use env variable"
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
SKIP_PULL=false
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
-c|--cleanup)
|
||||||
|
cleanup "$1"
|
||||||
|
;;
|
||||||
|
-p|--skip-pull)
|
||||||
|
SKIP_PULL=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--image)
|
||||||
|
SANDBOX_IMAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--skills-path)
|
||||||
|
SKILLS_PATH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
check_kubectl
|
||||||
|
check_cluster
|
||||||
|
|
||||||
|
# Pull image first to avoid Pod startup timeout
|
||||||
|
if [[ "$SKIP_PULL" == false ]]; then
|
||||||
|
pull_image
|
||||||
|
fi
|
||||||
|
|
||||||
|
apply_resources
|
||||||
|
verify_deployment
|
||||||
|
print_next_steps
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
@@ -16,7 +16,7 @@ http {
|
|||||||
|
|
||||||
# Upstream servers (using Docker service names)
|
# Upstream servers (using Docker service names)
|
||||||
upstream gateway {
|
upstream gateway {
|
||||||
server api:8001;
|
server gateway:8001;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream langgraph {
|
upstream langgraph {
|
||||||
@@ -24,7 +24,7 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
upstream frontend {
|
upstream frontend {
|
||||||
server web:3000;
|
server frontend:3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
|
|||||||
@@ -12,5 +12,11 @@ RUN pnpm config set store-dir ${PNPM_STORE_PATH}
|
|||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy frontend source code
|
||||||
|
COPY frontend ./frontend
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN sh -c "cd /app/frontend && pnpm install --frozen-lockfile"
|
||||||
|
|
||||||
# Expose Next.js dev server port
|
# Expose Next.js dev server port
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -60,32 +60,14 @@ init() {
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Build containers
|
# Build containers (dependencies are installed during build)
|
||||||
echo -e "${BLUE}Building containers...${NC}"
|
echo -e "${BLUE}Building containers...${NC}"
|
||||||
|
echo -e "${BLUE} - Frontend dependencies will be installed via Dockerfile${NC}"
|
||||||
|
echo -e "${BLUE} - Backend dependencies will be installed via Dockerfile${NC}"
|
||||||
cd "$DOCKER_DIR" && PNPM_STORE_PATH="$PNPM_STORE" $COMPOSE_CMD build
|
cd "$DOCKER_DIR" && PNPM_STORE_PATH="$PNPM_STORE" $COMPOSE_CMD build
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Install frontend dependencies
|
|
||||||
echo -e "${BLUE}Installing frontend dependencies...${NC}"
|
|
||||||
if ! (cd "$DOCKER_DIR" && PNPM_STORE_PATH="$PNPM_STORE" $COMPOSE_CMD run --rm -it --entrypoint "" web pnpm install --frozen-lockfile); then
|
|
||||||
echo -e "${YELLOW}Frontend dependencies installation failed or was interrupted${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✓ Frontend dependencies installed${NC}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Install backend dependencies
|
|
||||||
echo -e "${BLUE}Installing backend dependencies...${NC}"
|
|
||||||
if ! (cd "$DOCKER_DIR" && $COMPOSE_CMD run --rm -it --entrypoint "" api uv sync); then
|
|
||||||
echo -e "${YELLOW}Backend dependencies installation failed or was interrupted${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✓ Backend dependencies installed${NC}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo -e "${GREEN} ✓ Docker initialization complete!${NC}"
|
echo -e "${GREEN} ✓ Docker initialization complete!${NC}"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
@@ -111,8 +93,8 @@ start() {
|
|||||||
echo " 📡 API Gateway: http://localhost:2026/api/*"
|
echo " 📡 API Gateway: http://localhost:2026/api/*"
|
||||||
echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"
|
echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 📋 View logs: make docker-dev-logs"
|
echo " 📋 View logs: make docker-logs"
|
||||||
echo " 🛑 Stop: make docker-dev-stop"
|
echo " 🛑 Stop: make docker-stop"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,20 +103,24 @@ logs() {
|
|||||||
local service=""
|
local service=""
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--web)
|
--frontend)
|
||||||
service="web"
|
service="frontend"
|
||||||
echo -e "${BLUE}Viewing frontend logs...${NC}"
|
echo -e "${BLUE}Viewing frontend logs...${NC}"
|
||||||
;;
|
;;
|
||||||
--api)
|
--gateway)
|
||||||
service="api"
|
service="gateway"
|
||||||
echo -e "${BLUE}Viewing backend logs...${NC}"
|
echo -e "${BLUE}Viewing gateway logs...${NC}"
|
||||||
|
;;
|
||||||
|
--nginx)
|
||||||
|
service="nginx"
|
||||||
|
echo -e "${BLUE}Viewing nginx logs...${NC}"
|
||||||
;;
|
;;
|
||||||
"")
|
"")
|
||||||
echo -e "${BLUE}Viewing all logs...${NC}"
|
echo -e "${BLUE}Viewing all logs...${NC}"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${YELLOW}Unknown option: $1${NC}"
|
echo -e "${YELLOW}Unknown option: $1${NC}"
|
||||||
echo "Usage: $0 logs [--web|--api]"
|
echo "Usage: $0 logs [--frontend|--gateway]"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -176,8 +162,8 @@ help() {
|
|||||||
echo " start - Start all services in Docker (localhost:2026)"
|
echo " start - Start all services in Docker (localhost:2026)"
|
||||||
echo " restart - Restart all running Docker services"
|
echo " restart - Restart all running Docker services"
|
||||||
echo " logs [option] - View Docker development logs"
|
echo " logs [option] - View Docker development logs"
|
||||||
echo " --web View frontend logs only"
|
echo " --frontend View frontend logs only"
|
||||||
echo " --api View backend logs only"
|
echo " --gateway View gateway logs only"
|
||||||
echo " stop - Stop Docker development services"
|
echo " stop - Stop Docker development services"
|
||||||
echo " help - Show this help message"
|
echo " help - Show this help message"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
Reference in New Issue
Block a user