Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ Docker provides a consistent, isolated environment with all dependencies pre-con
```
`make docker-start` reads `config.yaml` and starts `provisioner` only for provisioner/Kubernetes sandbox mode.

Prefer this wrapper over invoking Compose yourself. If you do run Compose
directly, do it from the repository root (`docker compose -f docker/docker-compose-dev.yaml`),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] This direct command also needs to set DEER_FLOW_ROOT to the absolute checkout path. Running the documented command with it unset emits warnings and renders DEER_FLOW_HOST_BASE_DIR as /backend/.deer-flow and THREADS_HOST_PATH as /backend/.deer-flow/threads. Those are host-side paths in AIO/provisioner mode, so the command gets past the missing env file but remains misconfigured. Please document the platform-appropriate setting or make Compose derive it reliably.

not from inside `docker/`.

All services will start with hot-reload enabled:
- Frontend changes are automatically reloaded
- Backend changes trigger automatic restart
Expand Down
13 changes: 13 additions & 0 deletions backend/tests/test_compose_default_bind_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,16 @@ def _bind_address(mapping: str) -> str | None:

# ADDR:HOST:CONTAINER -> bound; HOST:CONTAINER or CONTAINER -> unbound.
return segments[0] if len(segments) >= 3 else None


def test_dev_compose_env_files_are_optional():
"""Missing .env files must not fail `docker compose -f docker/docker-compose-dev.yaml`."""
compose = yaml.safe_load(COMPOSE_PATHS["dev"].read_text(encoding="utf-8"))
expected = {
"provisioner": "../.env",
"frontend": "../frontend/.env",
"gateway": "../.env",
}
for service_name, path in expected.items():
entries = compose["services"][service_name]["env_file"]
assert entries == [{"path": path, "required": False}], f"{service_name} env_file must be optional; got: {entries!r}"
15 changes: 13 additions & 2 deletions backend/tests/test_docker_sandbox_mode_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,25 @@ def test_detect_mode_unknown_provider_falls_back_to_local():
assert _detect_mode_with_config(config) == "local"


def _seed_compose_env(tmp_root: Path) -> None:
"""Give prepare_compose_env the compose file and example env files it copies."""
(tmp_root / "docker-compose-dev.yaml").write_text("services: {}\n", encoding="utf-8")
(tmp_root / ".env.example").write_text("# test\n", encoding="utf-8")
frontend = tmp_root / "frontend"
frontend.mkdir()
(frontend / ".env.example").write_text("# test\n", encoding="utf-8")


@pytest.mark.parametrize("docker_command", ["logs --gateway", "restart"])
def test_compose_commands_set_deer_flow_root_before_compose(docker_command):
"""Log and restart commands should resolve mounts from the repository root."""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
_seed_compose_env(tmp_root)
command = f"""
source '{SCRIPT_PATH}'
PROJECT_ROOT='{tmpdir}'
DOCKER_DIR='{tmpdir}'
PROJECT_ROOT='{tmp_root}'
DOCKER_DIR='{tmp_root}'
COMPOSE_CMD=capture_compose
capture_compose() {{ test "${{DEER_FLOW_ROOT:-}}" = "$PROJECT_ROOT"; }}
unset DEER_FLOW_ROOT
Expand Down
23 changes: 19 additions & 4 deletions docker/docker-compose-dev.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# DeerFlow Development Environment
# Usage: docker-compose -f docker-compose-dev.yaml up --build
#
# Supported entry: from the repository root, run `make docker-start`.
# That wrapper creates missing .env files and invokes Compose from this
# directory with a relative filename.
#
# Direct Compose must also be run from the repository root:
# docker compose -f docker/docker-compose-dev.yaml up --build
# Do not reuse that -f path from inside docker/ — it resolves to
# docker/docker/docker-compose-dev.yaml and fails with file-not-found.
#
# env_file targets are optional so a missing ../.env or ../frontend/.env
# does not abort Compose on Windows ("file not found" /
# "Le fichier spécifique est introuvable").
#
# Services:
# - nginx: Reverse proxy (port 2026)
Expand Down Expand Up @@ -74,7 +86,8 @@ services:
# The same value must be set on the gateway side via config.yaml sandbox.provisioner_api_key.
- PROVISIONER_API_KEY=${PROVISIONER_API_KEY:-}
env_file:
- ../.env
- path: ../.env

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Please declare or avoid the new Compose 2.24 minimum. The long-form env_file required field is not understood by older Compose v2 clients, so configurations that previously ran make docker-start now fail while parsing this file, before the wrapper-created env files can help. The prerequisites currently list Docker Desktop or Engine without a Compose version floor, and the wrapper has no version check. Either preserve the previous syntax/support or document Compose >= 2.24 and fail early with an actionable version check, including for direct callers.

required: false
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
Expand Down Expand Up @@ -139,7 +152,8 @@ services:
- CI=true
- DEER_FLOW_INTERNAL_GATEWAY_BASE_URL=http://gateway:8001
env_file:
- ../frontend/.env
- path: ../frontend/.env
required: false
networks:
- deer-flow-dev
restart: unless-stopped
Expand Down Expand Up @@ -212,7 +226,8 @@ services:
- NO_PROXY=${NO_PROXY:-}${NO_PROXY:+,}localhost,127.0.0.1,::1,gateway,frontend,nginx,provisioner,openviking,host.docker.internal
- no_proxy=${no_proxy:-}${no_proxy:+,}localhost,127.0.0.1,::1,gateway,frontend,nginx,provisioner,openviking,host.docker.internal
env_file:
- ../.env
- path: ../.env
required: false
extra_hosts:
# For Linux: map host.docker.internal to host gateway
- "host.docker.internal:host-gateway"
Expand Down
65 changes: 46 additions & 19 deletions scripts/docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,46 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
DOCKER_DIR="$PROJECT_ROOT/docker"

# Docker Compose command with project name
COMPOSE_CMD="docker compose -p deer-flow-dev -f docker-compose-dev.yaml"
# Docker Compose command with project name.
# Use a filename relative to DOCKER_DIR (we always `cd` there) so Windows
# Docker Desktop does not receive a Git Bash `/c/...` path it cannot open.
# See https://github.com/bytedance/deer-flow/issues/2416
COMPOSE_FILE="docker-compose-dev.yaml"
COMPOSE_CMD="docker compose -p deer-flow-dev -f $COMPOSE_FILE"

ensure_from_example() {
local dest="$1"
local src="$2"
local label="$3"

if [ -f "$dest" ]; then
return 0
fi
if [ -f "$src" ]; then
cp "$src" "$dest"
echo -e "${BLUE}Created ${label} from $(basename "$src")${NC}"
return 0
fi
echo -e "${YELLOW}✗ ${label} not found and no $(basename "$src") to copy from.${NC}"
echo "Create ${dest} before starting Docker."
exit 1
}

# Compose env_file entries fail closed on Windows when .env is missing
# ("The specified file cannot be found" / "Le fichier spécifique est introuvable").
prepare_compose_env() {
if [ ! -f "$DOCKER_DIR/$COMPOSE_FILE" ]; then
echo -e "${YELLOW}✗ ${COMPOSE_FILE} not found at ${DOCKER_DIR}/${COMPOSE_FILE}${NC}"
echo "Run this from the DeerFlow repository root, e.g. 'make docker-start'."
echo "Do not run 'docker compose -f docker/${COMPOSE_FILE}' from inside docker/ — that resolves to docker/docker/${COMPOSE_FILE}."
exit 1
fi
if [ -z "$DEER_FLOW_ROOT" ]; then
export DEER_FLOW_ROOT="$PROJECT_ROOT"
fi
ensure_from_example "$PROJECT_ROOT/.env" "$PROJECT_ROOT/.env.example" ".env"
ensure_from_example "$PROJECT_ROOT/frontend/.env" "$PROJECT_ROOT/frontend/.env.example" "frontend/.env"
}

load_proxy_env_from_dotenv() {
local env_file="$PROJECT_ROOT/.env"
Expand Down Expand Up @@ -207,7 +245,7 @@ start() {
exit 1
fi
echo -e "${YELLOW}Mounting host Docker socket into gateway (DooD = host root-equivalent). See SECURITY.md.${NC}"
COMPOSE_CMD="$COMPOSE_CMD -f $DOCKER_DIR/docker-compose.dood.yaml"
COMPOSE_CMD="$COMPOSE_CMD -f docker-compose.dood.yaml"
fi

echo -e "${BLUE}Runtime: Gateway embedded agent runtime${NC}"
Expand Down Expand Up @@ -260,6 +298,7 @@ start() {
fi
fi

prepare_compose_env
load_proxy_env_from_dotenv

echo "Building and starting containers..."
Expand All @@ -283,12 +322,8 @@ start() {
logs() {
local service=""

# DEER_FLOW_ROOT is referenced in docker-compose-dev.yaml; set it before
# reading logs so Compose does not resolve mounted paths from an empty root.
if [ -z "$DEER_FLOW_ROOT" ]; then
export DEER_FLOW_ROOT="$PROJECT_ROOT"
fi

prepare_compose_env

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Please keep logs, stop, and restart read-only with respect to configuration. prepare_compose_env calls ensure_from_example for both env files, so these commands now mutate a fresh checkout and can even refuse to stop running containers if an example file is unavailable. Since the Compose entries are optional after this change, split the shared preflight into compose-file validation plus DEER_FLOW_ROOT setup, and run env-file creation only from start.


case "$1" in
--frontend)
service="frontend"
Expand Down Expand Up @@ -325,11 +360,7 @@ logs() {

# Stop Docker development environment
stop() {
# DEER_FLOW_ROOT is referenced in docker-compose-dev.yaml; set it before
# running compose down to suppress "variable is not set" warnings.
if [ -z "$DEER_FLOW_ROOT" ]; then
export DEER_FLOW_ROOT="$PROJECT_ROOT"
fi
prepare_compose_env
echo "Stopping Docker development services..."
cd "$DOCKER_DIR" && $COMPOSE_CMD down
echo "Cleaning up sandbox containers..."
Expand All @@ -339,11 +370,7 @@ stop() {

# Restart Docker development environment
restart() {
# DEER_FLOW_ROOT is referenced in docker-compose-dev.yaml; set it before
# restarting services so Compose resolves mounted paths from this checkout.
if [ -z "$DEER_FLOW_ROOT" ]; then
export DEER_FLOW_ROOT="$PROJECT_ROOT"
fi
prepare_compose_env
echo "========================================"
echo " Restarting DeerFlow Docker Services"
echo "========================================"
Expand Down
Loading