Compare commits

...
Author SHA1 Message Date
Saad Farooq fd2d92c31a perf(cli): merge cleanup steps and drop repeated WORKDIRs in generated Dockerfiles
Each Dockerfile instruction is a layer, and copy-based snapshotters pay a
full filesystem copy per layer. Emit the langgraph-api restore and build
tool removal as one RUN, and skip WORKDIRs that repeat the current one.
2026-11-04 08:41:33 -05:00
3 changed files with 40 additions and 29 deletions

No files matched your search

+19 -18
View File
@@ -154,12 +154,9 @@ def _get_pip_cleanup_lines(
pip_installer: Literal["uv", "pip"],
) -> str:
commands = [
f"""# -- Ensure user deps didn't inadvertently overwrite langgraph-api
RUN mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license && \
touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py
RUN PYTHONDONTWRITEBYTECODE=1 {install_cmd} --no-cache-dir --no-deps -e /api
# -- End of ensuring user deps didn't inadvertently overwrite langgraph-api --
# -- Removing build deps from the final image ~<:===~~~ --"""
"mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license && "
"touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py",
f"PYTHONDONTWRITEBYTECODE=1 {install_cmd} --no-cache-dir --no-deps -e /api",
]
if to_uninstall:
for pack in to_uninstall:
@@ -168,30 +165,31 @@ RUN PYTHONDONTWRITEBYTECODE=1 {install_cmd} --no-cache-dir --no-deps -e /api
f"Invalid build tool: {pack}; must be one of {', '.join(_BUILD_TOOLS)}"
)
packs_str = " ".join(sorted(to_uninstall))
commands.append(f"RUN pip uninstall -y {packs_str}")
commands.append(f"pip uninstall -y {packs_str}")
# Ensure the directories are removed entirely
packages_rm = " ".join(
f"/usr/local/lib/python*/site-packages/{pack}*" for pack in to_uninstall
)
if "pip" in to_uninstall:
packages_rm += ' && find /usr/local/bin -name "pip*" -delete || true'
commands.append(f"RUN rm -rf {packages_rm}")
commands.append(f"rm -rf {packages_rm}")
wolfi_packages_rm = " ".join(
f"/usr/lib/python*/site-packages/{pack}*" for pack in to_uninstall
)
if "pip" in to_uninstall:
wolfi_packages_rm += ' && find /usr/bin -name "pip*" -delete || true'
commands.append(f"RUN rm -rf {wolfi_packages_rm}")
commands.append(f"rm -rf {wolfi_packages_rm}")
if pip_installer == "uv":
commands.append(
f"RUN uv pip uninstall --system {packs_str} && rm /usr/bin/uv /usr/bin/uvx"
f"uv pip uninstall --system {packs_str} && rm /usr/bin/uv /usr/bin/uvx"
)
else:
if pip_installer == "uv":
commands.append(
"RUN rm /usr/bin/uv /usr/bin/uvx\n# -- End of build deps removal --"
)
return "\n".join(commands)
elif pip_installer == "uv":
commands.append("rm /usr/bin/uv /usr/bin/uvx")
# One RUN, since each layer is costly on copy-based snapshotters; subshells scope each `|| true`.
return (
"# -- Restore langgraph-api over user deps, then remove build deps --\n"
"RUN " + " && \\\n ".join(f"({command})" for command in commands)
)
def _parse_version(version_str: str) -> tuple[int, int]:
@@ -1608,7 +1606,10 @@ ADD {relpath} /deps/{name}
pip_installer=pip_installer,
),
"",
f"WORKDIR {local_deps.working_dir}" if local_deps.working_dir else "",
# The JS install step already set this WORKDIR.
f"WORKDIR {local_deps.working_dir}"
if local_deps.working_dir and not js_inst_str
else "",
]
)
@@ -1690,7 +1691,7 @@ def node_config_to_docker(
"",
os.linesep.join(env_vars),
"",
f"WORKDIR {build_workdir}",
f"WORKDIR {build_workdir}" if build_workdir != install_workdir else "",
"",
build_step,
]
+6
View File
@@ -65,6 +65,7 @@ class UvLockPlan:
@dataclass(slots=True)
class DockerBuildPlan:
lines: list[str]
workdir: str | None = None
def add_blank(self) -> None:
self.lines.append("")
@@ -73,6 +74,11 @@ class DockerBuildPlan:
self.lines.append(line)
def add_instruction(self, opcode: str, value: str | None = None) -> None:
# Repeating the current WORKDIR changes nothing but still costs a build step.
if opcode == "WORKDIR":
if value == self.workdir:
return
self.workdir = value
if value:
self.lines.append(f"{opcode} {value}")
else:
+15 -11
View File
@@ -1231,7 +1231,6 @@ ENV LANGGRAPH_AUTH='{"path": "./graphs/auth.mts:auth"}'
ENV LANGGRAPH_UI='{"agent": "./graphs/agent.ui.jsx"}'
ENV LANGGRAPH_UI_CONFIG='{"shared": ["nuqs"]}'
ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}'
WORKDIR /deps/unit_tests
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
@@ -1422,7 +1421,6 @@ ENV LANGGRAPH_AUTH='{"path": "./graphs/auth.mts:auth"}'
ENV LANGGRAPH_UI='{"agent": "./graphs/agent.ui.jsx"}'
ENV LANGGRAPH_UI_CONFIG='{"shared": ["nuqs"]}'
ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}'
WORKDIR /deps/unit_tests
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
@@ -1550,8 +1548,7 @@ ENV NODE_VERSION=20
WORKDIR /deps/outer-unit_tests/unit_tests
RUN npm i && tsx /api/langgraph_api/js/build.mts
# -- End of JS dependencies install --
{FORMATTED_CLEANUP_LINES}
WORKDIR /deps/outer-unit_tests/unit_tests"""
{FORMATTED_CLEANUP_LINES}"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
@@ -1595,8 +1592,7 @@ ENV NODE_VERSION=22
WORKDIR /deps/outer-unit_tests/unit_tests
RUN npm i && tsx /api/langgraph_api/js/build.mts
# -- End of JS dependencies install --
{FORMATTED_CLEANUP_LINES}
WORKDIR /deps/outer-unit_tests/unit_tests"""
{FORMATTED_CLEANUP_LINES}"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
@@ -2152,7 +2148,8 @@ def test_config_to_docker_uv_lock_detects_js_pm_from_target_package_root():
config_path, config, base_image="langchain/langgraph-api:0.2.47"
)
assert "WORKDIR /deps/workspace/apps/agent" in docker
# Package install, JS install and runtime all share this dir: one WORKDIR.
assert docker.splitlines().count("WORKDIR /deps/workspace/apps/agent") == 1
assert (
"RUN pnpm i --frozen-lockfile && tsx /api/langgraph_api/js/build.mts"
in docker
@@ -2803,7 +2800,7 @@ def test_config_retain_build_tools():
"/usr/local/lib/python*/site-packages/" + pckg + "*" in docker_true
for pckg in _BUILD_TOOLS
)
assert "RUN pip uninstall -y pip setuptools wheel" not in docker_true
assert "(pip uninstall -y pip setuptools wheel)" not in docker_true
config_false = validate_config(
{**copy.deepcopy(base_config), "keep_pkg_tools": False}
)
@@ -2814,7 +2811,7 @@ def test_config_retain_build_tools():
"/usr/local/lib/python*/site-packages/" + pckg + "*" in docker_false
for pckg in _BUILD_TOOLS
)
assert "RUN pip uninstall -y pip setuptools wheel" in docker_false
assert "(pip uninstall -y pip setuptools wheel)" in docker_false
config_list = validate_config(
{**copy.deepcopy(base_config), "keep_pkg_tools": ["pip", "setuptools"]}
)
@@ -2829,8 +2826,15 @@ def test_config_retain_build_tools():
"/usr/local/lib/python*/site-packages/" + pckg + "*" in docker_list
for pckg in ("pip", "setuptools")
)
assert "RUN pip uninstall -y wheel" in docker_list
assert "RUN pip uninstall -y pip setuptools" not in docker_list
assert "(pip uninstall -y wheel)" in docker_list
assert "(pip uninstall -y pip setuptools)" not in docker_list
def test_pip_cleanup_is_a_single_run_with_scoped_fallbacks():
cleanup = _get_pip_cleanup_lines("uv pip install --system", _BUILD_TOOLS, "uv")
assert cleanup.count("RUN ") == 1
# Each `|| true` must close its own subshell so it can't mask later steps.
assert cleanup.count("|| true)") == cleanup.count("|| true") == 2
# config_to_compose