fix(docker): add 'make prod-logs' entry point and hint when dev logs are empty (#5538)

* fix(docker): add 'make prod-logs' entry point and hint when dev logs are empty

`make up` starts the production stack (deploy.sh: project `deer-flow`,
docker/docker-compose.yaml) while `make docker-logs` tails the dev stack
(project `deer-flow-dev`, docker-compose-dev.yaml), so after `make up`
it printed nothing (#5529), and no make entry point showed production
logs at all.

- scripts/docker.sh logs gains `--prod`: targets the stack deploy.sh
  started, passes --env-file ../.env when present, and exports the same
  interpolation defaults deploy.sh exports before every compose call —
  without them the production volume specs fail to parse on checkouts
  without .env.
- dev-only `logs` with no running containers now prints a hint pointing
  at `make prod-logs` instead of staying silent.
- Makefile gains `prod-logs`, listed under Docker Production Commands.

New tests cover production targeting and the empty-state hint; they are
red on unfixed main and green here. Verified live: with a deer-flow
redis running, `logs --prod --redis` streams its logs.

Fixes #5529

* fix(docker): append --env-file after compose detection rebuilds COMPOSE_CMD

compose_preflight() probes the Compose binary and rebuilds COMPOSE_CMD,
so an --env-file appended before it was silently dropped. Append it
after preflight instead, and drive the regression test through the real
detection path (stub Docker Compose version v5.3.1, not require_compose_version)
so the append cannot regress silently.

Reviewed-in: #5538
This commit is contained in:
xiaodu55 2026-09-19 11:10:32 +08:00 committed by GitHub
parent 82cf57a9c3
commit 859b105b40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 153 additions and 36 deletions

View File

@ -1,6 +1,6 @@
# DeerFlow - Unified Development Environment
.PHONY: help config config-upgrade check check-agent-guidance install extension-install extension-upgrade extension-list extension-enable extension-disable extension-remove setup doctor support-bundle detect-thread-boundaries detect-blocking-io dev dev-daemon start start-daemon nginx stop up down clean docker-init docker-start docker-stop docker-logs docker-logs-frontend docker-logs-gateway docker-logs-redis setup-sandbox
.PHONY: help config config-upgrade check check-agent-guidance install extension-install extension-upgrade extension-list extension-enable extension-disable extension-remove setup doctor support-bundle detect-thread-boundaries detect-blocking-io dev dev-daemon start start-daemon nginx stop up down prod-logs clean docker-init docker-start docker-stop docker-logs docker-logs-frontend docker-logs-gateway docker-logs-redis setup-sandbox
BASH ?= bash
BACKEND_UV_RUN = cd backend && uv run
@ -50,6 +50,7 @@ help:
@echo ""
@echo "Docker Production Commands:"
@echo " make up - Build and start production Docker services (localhost:2026)"
@echo " make prod-logs - Follow production Docker logs (stack started by 'make up')"
@echo " make down - Stop and remove production Docker containers"
@echo ""
@echo "Docker Development Commands:"
@ -217,3 +218,7 @@ up:
# Stop and remove production containers
down:
@$(RUN_SHELL_SCRIPT) ./scripts/deploy.sh down
# Follow production container logs (stack started by `make up`)
prod-logs:
@$(RUN_SHELL_SCRIPT) ./scripts/docker.sh logs --prod

View File

@ -313,6 +313,83 @@ require_compose_version
assert "too old" in result.stdout
@pytest.mark.parametrize("args", ["logs --prod", "logs --prod --gateway"])
def test_logs_prod_targets_production_stack(args):
"""`logs --prod` must tail the stack deploy.sh started, not the dev project.
`make up` runs scripts/deploy.sh (project `deer-flow`, docker-compose.yaml)
while the dev default is project `deer-flow-dev`, so `make docker-logs`
after `make up` printed nothing (#5529). The production entry point must
target the same project and interpolate the same .env.
Compose detection is NOT stubbed here: it rebuilds COMPOSE_CMD, and the
appended `--env-file` must survive that rebuild (#5538 review).
"""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
(tmp_root / "docker-compose.yaml").write_text("services: {}\n", encoding="utf-8")
(tmp_root / ".env").write_text("KEEP=me\n", encoding="utf-8")
marker = tmp_root / "prod_invoke.txt"
command = f"""
source '{SCRIPT_PATH}'
PROJECT_ROOT='{tmp_root}'
DOCKER_DIR='{tmp_root}'
docker() {{
if [ "$1" = compose ] && [ "$2" = version ]; then
echo '2.41.0'
return 0
fi
if [ "$1" = compose ]; then
printf '%s\\n' "$*" "DEER_FLOW_HOME=${{DEER_FLOW_HOME:-unset}}" > '{marker}'
return 0
fi
command docker "$@"
}}
unset DEER_FLOW_ROOT DEER_FLOW_HOME DEER_FLOW_CONFIG_PATH
unset DEER_FLOW_EXTENSIONS_CONFIG_PATH DEER_FLOW_REPO_ROOT
unset BETTER_AUTH_SECRET DEER_FLOW_INTERNAL_AUTH_TOKEN
{args}
"""
subprocess.check_call([BASH_EXECUTABLE, "-lc", command])
recorded = marker.read_text(encoding="utf-8")
assert "-p deer-flow " in recorded, recorded
assert "-f docker-compose.yaml" in recorded, recorded
assert "--env-file ../.env" in recorded, recorded
assert "logs" in recorded, recorded
# deploy.sh exports interpolation defaults before every compose call;
# `logs --prod` must too, or the volume specs fail to parse.
assert "DEER_FLOW_HOME=" in recorded, recorded
assert "DEER_FLOW_HOME=unset" not in recorded, recorded
def test_logs_without_dev_containers_hints_at_prod_logs():
"""Silent empty output is the #5529 report: point at the production entry point."""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
_seed_compose_file(tmp_root)
command = f"""
source '{SCRIPT_PATH}'
PROJECT_ROOT='{tmp_root}'
DOCKER_DIR='{tmp_root}'
require_compose_version() {{ :; }}
COMPOSE_CMD=true
unset DEER_FLOW_ROOT
logs
"""
result = subprocess.run(
[BASH_EXECUTABLE, "-lc", command],
capture_output=True,
text=True,
encoding="utf-8",
)
assert result.returncode == 0, result.stdout + result.stderr
assert "make prod-logs" in result.stdout
def test_aio_dood_socket_preflight_allows_windows_when_docker_reachable():
"""Windows Git Bash without /var/run/docker.sock proceeds when Docker daemon is reachable."""
with tempfile.TemporaryDirectory() as tmpdir:

View File

@ -16,12 +16,15 @@ DOCKER_DIR="$PROJECT_ROOT/docker"
# 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.
COMPOSE_FILE="docker-compose-dev.yaml"
# Dev stack project name. `logs --prod` swaps both values for the production
# stack started by `make up` (scripts/deploy.sh: project `deer-flow`).
COMPOSE_PROJECT="deer-flow-dev"
# Selected by require_compose_version: prefer the V2 plugin, else hyphenated binary.
# Kept as an array so "docker compose" stays two words under set -u / quoting.
COMPOSE_BIN=(docker compose)
_refresh_compose_cmd() {
COMPOSE_CMD="${COMPOSE_BIN[*]} -p deer-flow-dev -f ${COMPOSE_FILE}"
COMPOSE_CMD="${COMPOSE_BIN[*]} -p ${COMPOSE_PROJECT} -f ${COMPOSE_FILE}"
}
_refresh_compose_cmd
@ -416,43 +419,73 @@ start() {
echo ""
}
# View Docker development logs
# View Docker logs. The dev stack (make docker-start) is the default;
# `--prod` tails the production stack started by `make up` (scripts/deploy.sh).
logs() {
local service=""
local is_prod=0
while [ $# -gt 0 ]; do
case "$1" in
--prod)
is_prod=1
;;
--frontend|--gateway|--nginx|--redis|--provisioner)
if [ -n "$service" ]; then
echo -e "${YELLOW}Only one service option is allowed (got --$service and $1).${NC}"
exit 1
fi
service="${1#--}"
;;
*)
echo -e "${YELLOW}Unknown option: $1${NC}"
echo "Usage: $0 logs [--prod] [--frontend|--gateway|--nginx|--redis|--provisioner]"
exit 1
;;
esac
shift
done
if [ "$is_prod" = 1 ]; then
# Target the same project deploy.sh started. Relative paths: this
# runs with cwd=$DOCKER_DIR.
COMPOSE_FILE="docker-compose.yaml"
COMPOSE_PROJECT="deer-flow"
# deploy.sh exports these before every compose invocation so the
# volume specs in docker-compose.yaml interpolate; without them even
# `logs` fails to parse the file on checkouts without a .env.
export DEER_FLOW_HOME="${DEER_FLOW_HOME:-$PROJECT_ROOT/backend/.deer-flow}"
export DEER_FLOW_CONFIG_PATH="${DEER_FLOW_CONFIG_PATH:-$DEER_FLOW_HOME/config.yaml}"
export DEER_FLOW_EXTENSIONS_CONFIG_PATH="${DEER_FLOW_EXTENSIONS_CONFIG_PATH:-$DEER_FLOW_HOME/extensions_config.json}"
export DEER_FLOW_REPO_ROOT="${DEER_FLOW_REPO_ROOT:-$PROJECT_ROOT}"
export BETTER_AUTH_SECRET="${BETTER_AUTH_SECRET:-placeholder}"
export DEER_FLOW_INTERNAL_AUTH_TOKEN="${DEER_FLOW_INTERNAL_AUTH_TOKEN:-placeholder}"
elif [ -z "$service" ]; then
# The dev and production stacks use different compose projects, so
# `make docker-logs` after `make up` would exit with empty output;
# point at the production entry point instead of staying silent.
if [ -z "$(cd "$DOCKER_DIR" && $COMPOSE_CMD ps -q 2>/dev/null)" ]; then
echo -e "${YELLOW}No dev containers are running.${NC}"
echo "Started the production stack with 'make up'? View its logs with 'make prod-logs'."
fi
fi
compose_preflight
case "$1" in
--frontend)
service="frontend"
echo -e "${BLUE}Viewing frontend logs...${NC}"
;;
--gateway)
service="gateway"
echo -e "${BLUE}Viewing gateway logs...${NC}"
;;
--nginx)
service="nginx"
echo -e "${BLUE}Viewing nginx logs...${NC}"
;;
--redis)
service="redis"
echo -e "${BLUE}Viewing redis logs...${NC}"
;;
--provisioner)
service="provisioner"
echo -e "${BLUE}Viewing provisioner logs...${NC}"
;;
"")
echo -e "${BLUE}Viewing all logs...${NC}"
;;
*)
echo -e "${YELLOW}Unknown option: $1${NC}"
echo "Usage: $0 logs [--frontend|--gateway|--nginx|--redis|--provisioner]"
exit 1
;;
esac
# Append --env-file only after compose_preflight(): its Compose detection
# may rebuild COMPOSE_CMD, which would drop anything appended before it.
if [ "$is_prod" = 1 ] && [ -f "$PROJECT_ROOT/.env" ]; then
COMPOSE_CMD="$COMPOSE_CMD --env-file ../.env"
fi
if [ -n "$service" ]; then
echo -e "${BLUE}Viewing $service logs...${NC}"
elif [ "$is_prod" = 1 ]; then
echo -e "${BLUE}Viewing production stack logs...${NC}"
else
echo -e "${BLUE}Viewing all logs...${NC}"
fi
cd "$DOCKER_DIR" && $COMPOSE_CMD logs -f $service
}
@ -493,7 +526,8 @@ help() {
echo " init - Pull the sandbox image (speeds up first Pod startup)"
echo " start - Start Docker services (auto-detects sandbox mode from config.yaml)"
echo " restart - Restart all running Docker services"
echo " logs [option] - View Docker development logs"
echo " logs [option] - View Docker logs (dev stack by default, production with --prod)"
echo " --prod View production stack logs (containers from 'make up')"
echo " --frontend View frontend logs only"
echo " --gateway View gateway logs only"
echo " --nginx View nginx logs only"
@ -518,7 +552,8 @@ main() {
restart
;;
logs)
logs "$2"
shift
logs "$@"
;;
stop)
stop