Skip to content

Commit 514fff2

Browse files
authored
fix: Dockerfile and start script (#4)
1 parent 8131257 commit 514fff2

File tree

2 files changed

+268
-42
lines changed

2 files changed

+268
-42
lines changed

Dockerfile

Lines changed: 232 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,245 @@
1-
# Dockerfile template for Droq nodes
2-
# This is an agnostic template - customize as needed for your node
1+
# syntax=docker/dockerfile:1
2+
# Dockerfile for LFX Tool Executor Node
3+
# Build from repo root: docker build -f Dockerfile -t droqai/lfx-tool-executor-node:latest .
34

4-
FROM python:3.11-slim
5+
################################
6+
# BUILDER STAGE
7+
# Build dependencies and Langflow
8+
################################
9+
FROM ghcr.io/astral-sh/uv:python3.12-alpine AS builder
10+
11+
# Install build dependencies
12+
# Retry on failure to handle transient network issues
13+
RUN set -e; \
14+
for i in 1 2 3; do \
15+
apk update && \
16+
apk add --no-cache \
17+
build-base \
18+
libaio-dev \
19+
linux-headers && \
20+
break || sleep 5; \
21+
done
522

6-
# Set working directory
723
WORKDIR /app
824

9-
# Install system dependencies
10-
# Uncomment and add system packages as needed:
11-
# RUN apt-get update && apt-get install -y \
12-
# gcc \
13-
# g++ \
14-
# make \
15-
# curl \
16-
# && rm -rf /var/lib/apt/lists/*
17-
18-
# Install uv
19-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
20-
21-
# Copy dependency files and source code
22-
COPY pyproject.toml README.md ./
23-
COPY uv.lock* ./
24-
COPY src/ ./src/
25-
COPY lfx /app/lfx
25+
# Enable bytecode compilation
26+
ENV UV_COMPILE_BYTECODE=1
27+
ENV UV_LINK_MODE=copy
28+
29+
# Copy dependency files first (for better caching)
30+
# Copy uv.lock file to use the same dependency resolution as local setup
31+
COPY uv.lock /app/uv.lock
32+
COPY pyproject.toml /app/pyproject.toml
33+
COPY README.md /app/README.md
34+
35+
# Copy Langflow dependency files
36+
COPY lfx/pyproject.toml /app/lfx/pyproject.toml
37+
COPY lfx/README.md /app/lfx/README.md
38+
39+
# Copy Langflow source (needed for installation)
40+
COPY lfx/src /app/lfx/src
41+
42+
# Copy executor node source
43+
COPY src/node /app/src/node
44+
45+
# Create venv first
46+
RUN --mount=type=cache,target=/root/.cache/uv \
47+
uv venv
48+
49+
# Install lfx package FIRST to pin langchain-core<1.0.0 (which has langchain_core.memory)
50+
# This ensures langchain_core.memory is available before any other packages
51+
RUN --mount=type=cache,target=/root/.cache/uv \
52+
cd /app/lfx && \
53+
uv pip install --python /app/.venv/bin/python --no-cache -e .
54+
55+
# Explicitly pin langchain-core to <1.0.0 to prevent any upgrades
56+
# This is critical - langchain-core>=1.0.0 doesn't have langchain_core.memory
57+
RUN --mount=type=cache,target=/root/.cache/uv \
58+
uv pip install --python /app/.venv/bin/python --no-cache \
59+
"langchain-core>=0.3.66,<1.0.0" --force-reinstall
60+
61+
# Install langchain package which provides langchain_core.memory
62+
RUN --mount=type=cache,target=/root/.cache/uv \
63+
uv pip install --python /app/.venv/bin/python --no-cache \
64+
"langchain~=0.3.23"
65+
66+
# Now sync other dependencies from lock file (executor node dependencies)
67+
RUN --mount=type=cache,target=/root/.cache/uv \
68+
uv sync --frozen --no-dev || echo "Warning: Some dependencies may conflict, continuing..."
69+
70+
# Install the executor node package itself (editable install like local)
71+
# Note: langchain-chroma and langchain-docling may fail on Alpine aarch64 due to missing wheels
72+
# Install package without these problematic dependencies first, then try to install them separately (allow failure)
73+
RUN --mount=type=cache,target=/root/.cache/uv \
74+
cd /app && \
75+
uv pip install --python /app/.venv/bin/python --no-cache -e . --no-deps && \
76+
uv pip install --python /app/.venv/bin/python --no-cache \
77+
"fastapi>=0.115.0,<1.0.0" \
78+
"uvicorn[standard]>=0.34.0,<1.0.0" \
79+
"pydantic>=2.0.0,<3.0.0" \
80+
"python-dotenv>=1.0.0,<2.0.0" \
81+
"structlog>=25.0.0,<26.0.0" \
82+
"nats-py>=2.6.0,<3.0.0" \
83+
"httpx>=0.27.0,<1.0.0" \
84+
"langchain-core>=0.3.79,<0.4.0" \
85+
"langchain==0.3.23" \
86+
"langchain-anthropic==0.3.14" \
87+
"langchain-astradb>=0.6.1,<1.0.0" \
88+
"langchain-aws==0.2.33" \
89+
"langchain-cohere==0.3.3" \
90+
"langchain-community>=0.3.21,<1.0.0" \
91+
"langchain-elasticsearch==0.3.0" \
92+
"langchain-google-calendar-tools==0.0.1" \
93+
"langchain-google-community==2.0.3" \
94+
"langchain-google-genai==2.0.6" \
95+
"langchain-google-vertexai>=2.0.7,<3.0.0" \
96+
"langchain-graph-retriever==0.8.0" \
97+
"langchain-groq==0.2.1" \
98+
"langchain-huggingface==0.3.1" \
99+
"langchain-milvus==0.1.7" \
100+
"langchain-mistralai==0.2.3" \
101+
"langchain-mongodb==0.7.0" \
102+
"langchain-nvidia-ai-endpoints==0.3.8" \
103+
"langchain-ollama==0.2.1" \
104+
"langchain-openai>=0.2.12,<1.0.0" \
105+
"langchain-pinecone>=0.2.8,<1.0.0" \
106+
"langchain-sambanova==0.1.0" \
107+
"langchain-unstructured>=0.1.5" \
108+
"langchain-ibm>=0.3.8" \
109+
"nanoid>=2.0.0" && \
110+
(uv pip install --python /app/.venv/bin/python --no-cache "langchain-chroma>=0.2.6,<1.0.0" || \
111+
echo "Warning: langchain-chroma installation failed (onnxruntime may not have Python 3.12 wheels for Alpine aarch64)") && \
112+
(uv pip install --python /app/.venv/bin/python --no-cache "langchain-docling>=1.1.0" || \
113+
echo "Warning: langchain-docling installation failed (torch may not have Python 3.12 wheels for Alpine aarch64)")
114+
115+
# Install langchain integration packages compatible with langchain-core<1.0.0
116+
# Pin versions to match local working installation (langchain-core 0.3.79, pydantic 2.12.4)
117+
RUN --mount=type=cache,target=/root/.cache/uv \
118+
uv pip install --python /app/.venv/bin/python --no-cache \
119+
"langchain-openai==0.2.14" \
120+
"langchain-anthropic>=0.1.13,<0.2.0" \
121+
"langchain-community>=0.0.38,<0.1.0" \
122+
"langchain-google-genai>=0.0.6,<0.1.0" \
123+
"langchain-ollama>=0.3.5,<0.4.0" || echo "Warning: Some langchain packages failed to install"
124+
125+
# Re-pin langchain-core after installing integration packages (they might try to upgrade it)
126+
RUN --mount=type=cache,target=/root/.cache/uv \
127+
uv pip install --python /app/.venv/bin/python --no-cache \
128+
"langchain-core>=0.3.66,<1.0.0" --force-reinstall
129+
130+
# Re-install langchain package after re-pinning langchain-core (uv sync or re-pin might have removed it)
131+
RUN --mount=type=cache,target=/root/.cache/uv \
132+
uv pip install --python /app/.venv/bin/python --no-cache \
133+
"langchain~=0.3.23" || echo "Warning: langchain installation failed"
134+
135+
# Re-install langchain-openai after re-pinning to ensure compatibility with langchain-core<1.0.0
136+
RUN --mount=type=cache,target=/root/.cache/uv \
137+
uv pip install --python /app/.venv/bin/python --no-cache \
138+
"langchain-openai==0.2.14" --force-reinstall || echo "Warning: langchain-openai re-installation failed"
139+
140+
# Ensure all lfx dependencies are installed (some might not be in the lock file)
141+
# NOTE: aiofile and aiofiles are DIFFERENT packages - both are required!
142+
# Also ensure langchain is installed (required by AgentComponent and other components)
143+
RUN --mount=type=cache,target=/root/.cache/uv \
144+
uv pip install --python /app/.venv/bin/python --no-cache \
145+
"langchain~=0.3.23" \
146+
"nanoid>=2.0.0,<3.0.0" \
147+
"platformdirs>=4.3.8,<5.0.0" \
148+
"aiofile>=3.8.0,<4.0.0" \
149+
"aiofiles>=24.1.0,<25.0.0" \
150+
"pillow>=10.0.0,<13.0.0" \
151+
"emoji>=2.0.0,<3.0.0" \
152+
"asyncer>=0.0.8,<1.0.0" \
153+
"cachetools>=5.5.2,<6.0.0" \
154+
"chardet>=5.2.0,<6.0.0" \
155+
"defusedxml>=0.7.1,<1.0.0" \
156+
"docstring-parser>=0.16,<1.0.0" \
157+
"json-repair>=0.30.3,<1.0.0" \
158+
"loguru>=0.7.3,<1.0.0" \
159+
"networkx>=3.4.2,<4.0.0" \
160+
"orjson>=3.10.15,<4.0.0" \
161+
"passlib>=1.7.4,<2.0.0" \
162+
"pydantic>=2.12.4,<3.0.0" \
163+
"pydantic-settings>=2.10.1,<3.0.0" \
164+
"rich>=13.0.0,<14.0.0" \
165+
"tomli>=2.2.1,<3.0.0" \
166+
"typer>=0.16.0,<1.0.0" \
167+
"typing-extensions>=4.14.0,<5.0.0" \
168+
"validators>=0.34.0,<1.0.0" \
169+
"qdrant-client>=1.15.1,<2.0.0"
170+
171+
# Install langchain-experimental for PythonREPLComponent and other experimental features
172+
RUN --mount=type=cache,target=/root/.cache/uv \
173+
uv pip install --python /app/.venv/bin/python --no-cache \
174+
"langchain-experimental>=0.0.50,<1.0.0" || echo "Warning: langchain-experimental installation failed"
175+
176+
# Re-pin langchain-core after langchain-experimental (it might have upgraded langchain-core to >=1.0.0)
177+
RUN --mount=type=cache,target=/root/.cache/uv \
178+
uv pip install --python /app/.venv/bin/python --no-cache \
179+
"langchain-core>=0.3.66,<1.0.0" --force-reinstall
180+
181+
# Copy node.json mapping file and startup script
26182
COPY node.json /app/node.json
183+
COPY start-local.sh /app/start-local.sh
184+
185+
################################
186+
# RUNTIME STAGE
187+
# Minimal runtime image
188+
################################
189+
FROM python:3.12-alpine AS runtime
27190

28-
# Install project dependencies
29-
RUN uv pip install --system --no-cache -e .
191+
# Install runtime dependencies with retry (including bash for start-local.sh)
192+
RUN set -e; \
193+
for i in 1 2 3; do \
194+
apk update && \
195+
apk add --no-cache curl bash && \
196+
break || sleep 5; \
197+
done
30198

31-
# Create non-root user for security
32-
RUN useradd -m -u 1000 nodeuser && chown -R nodeuser:nodeuser /app
33-
USER nodeuser
199+
# Create non-root user
200+
RUN adduser -D -u 1000 -G root -h /app -s /sbin/nologin executor
201+
202+
WORKDIR /app
203+
204+
# Copy the virtual environment from builder (created by uv sync)
205+
# This ensures all dependencies are installed exactly as in local setup
206+
COPY --from=builder --chown=executor:root /app/.venv /app/.venv
207+
208+
# Copy application code
209+
# Copy node package to /app/node (so "import node" works)
210+
COPY --from=builder --chown=executor:root /app/src/node /app/node
211+
COPY --from=builder --chown=executor:root /app/lfx/src /app/lfx/src
212+
COPY --from=builder --chown=executor:root /app/node.json /node.json
213+
COPY --from=builder --chown=executor:root /app/README.md /app/README.md
214+
COPY --from=builder --chown=executor:root /app/pyproject.toml /app/pyproject.toml
215+
COPY --from=builder /app/start-local.sh /app/start-local.sh
216+
217+
# Add venv to PATH so Python uses the venv's packages
218+
ENV PATH="/app/.venv/bin:$PATH"
219+
220+
# Make startup script executable and verify it exists (as root, before switching users)
221+
RUN chmod +x /app/start-local.sh && \
222+
chown executor:root /app/start-local.sh && \
223+
ls -la /app/start-local.sh
34224

35225
# Set environment variables
36-
ENV PYTHONPATH=/app/lfx/src:/app/src
226+
ENV PYTHONPATH=/app:/app/lfx/src
37227
ENV PYTHONUNBUFFERED=1
228+
ENV HOST=0.0.0.0
229+
ENV PORT=8005
230+
ENV LANGFLOW_EXECUTOR_NODE_URL=http://localhost:8005
231+
ENV DOCKER_CONTAINER=1
232+
ENV RELOAD=false
233+
234+
# Switch to non-root user
235+
USER executor
38236

39-
# Optional: Health check
40-
# Uncomment and customize as needed:
41-
# HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
42-
# CMD python -c "import sys; sys.exit(0)"
237+
# Expose port
238+
EXPOSE 8005
43239

44-
# Run the node
45-
CMD ["uv", "run", "lfx-tool-executor-node"]
240+
# Health check
241+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
242+
CMD curl -f http://localhost:8005/health || exit 1
46243

244+
# Run the executor node using start-local.sh
245+
CMD ["/bin/bash", "./start-local.sh"]

start-local.sh

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,41 @@ PORT="${1:-8005}"
55

66
echo "🚀 Starting LFx Tool Executor Node on port ${PORT}"
77

8-
# Install local lfx package in editable mode so executor node uses latest code
9-
if [ -d "lfx/src" ]; then
10-
echo "Installing local lfx package (editable mode)..."
11-
uv pip install -e ./lfx/src >/dev/null 2>&1 || true
8+
# Check if running in Docker
9+
if [ -n "${DOCKER_CONTAINER:-}" ]; then
10+
echo "Running in Docker container - using venv Python directly"
11+
# Use the venv that was copied from the builder stage
12+
if [ -d "/app/.venv" ]; then
13+
export PATH="/app/.venv/bin:$PATH"
14+
PYTHON_CMD="/app/.venv/bin/python"
15+
else
16+
PYTHON_CMD="python"
17+
fi
18+
19+
# Set PYTHONPATH (already set in Dockerfile, but ensure it's correct)
20+
export PYTHONPATH="/app:/app/lfx/src:${PYTHONPATH:-}"
21+
22+
# Run using uvicorn directly (like runtime executor node)
23+
cd /app
24+
exec $PYTHON_CMD -m uvicorn node.api:app --host "${HOST:-0.0.0.0}" --port "${PORT}" --log-level "${LOG_LEVEL:-info}"
25+
else
26+
# Local development - check if uv is installed
27+
if ! command -v uv >/dev/null 2>&1; then
28+
echo "Error: uv is not installed. Please install it first:"
29+
echo " pipx install uv"
30+
echo " or visit: https://github.com/astral-sh/uv"
31+
exit 1
32+
fi
33+
34+
# Install local lfx package in editable mode so executor node uses latest code
35+
if [ -d "lfx/src" ]; then
36+
echo "Installing local lfx package (editable mode)..."
37+
uv pip install -e ./lfx/src >/dev/null 2>&1 || true
38+
fi
39+
40+
# Add lfx to PYTHONPATH so components can be imported directly (local source takes precedence)
41+
export PYTHONPATH="$(pwd)/lfx/src:$(pwd)/src:${PYTHONPATH:-}"
42+
43+
uv run lfx-tool-executor-node "${PORT}"
1244
fi
1345

14-
# Add lfx to PYTHONPATH so components can be imported directly (local source takes precedence)
15-
export PYTHONPATH="$(pwd)/lfx/src:$(pwd)/src:${PYTHONPATH:-}"
16-
17-
uv run lfx-tool-executor-node "${PORT}"
18-

0 commit comments

Comments
 (0)