Run node code on the venv Fluksio was installed into

The workflow this serves: make a venv, install what you work with, then `pip
install fluksio` into the same one. Building a second environment beside it
was exactly wrong — the packages the nodes need are already here, and the
Modules screen was asking for them a second time.

`NODE_VENV=auto` (the default) adopts that venv. It declines in the three
cases where adopting would be wrong: `managed` says otherwise, a managed venv
already exists and may hold packages somebody installed on purpose, or the
engine is not running from a venv at all. The images set `managed`, since the
venv in them holds the app and nothing of anybody else's.

An adopted venv is never written to. `uv pip sync` makes a venv hold exactly
the manifest, so pointed at somebody's own environment it uninstalls their
work and the engine with it — `sync()` refuses outright and `reconcile()`
returns before it can be called at startup, which is where that would have
happened first. The Modules screen lists what is installed and drops its
editor; `pip` is how that environment changes.

`fluksio serve` now names the interpreter node code runs on, which is the
thing a data scientist most needs to know at that moment. `fluksio-worker`
already defaulted `--python` to its own interpreter, so a GPU box works the
same way — that was only ever undocumented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ue1tkFWB1bcGy3aWhCKpU
This commit is contained in:
2026-08-24 10:35:13 +02:00
co-authored by Claude Fable 5
parent 68fa5527b1
commit fea57064f9
15 changed files with 357 additions and 69 deletions
+5
View File
@@ -1521,6 +1521,11 @@ export const ModulesInfoSchema = {
type: 'boolean',
title: 'Applied',
default: false
},
adopted: {
type: 'boolean',
title: 'Adopted',
default: false
}
},
type: 'object',
+1
View File
@@ -583,6 +583,7 @@ export type ModulesInfo = {
requirements?: string;
packages?: Array<ModulePackage>;
applied?: boolean;
adopted?: boolean;
};
export type NewPassword = {
+59 -42
View File
@@ -62,64 +62,81 @@ function Modules() {
const requirements = draft ?? stored
const packages = data?.packages ?? []
const dirty = requirements !== stored
// Fluksio was installed into an environment somebody else made, and node
// code runs on it. Installing from here would mean `uv pip sync`, which
// holds a venv to exactly one list — and would uninstall their work.
const adopted = data?.adopted ?? false
return (
<div className="grid gap-6">
<div className="grid gap-1">
<h1 className="text-2xl">Modules</h1>
<p className="text-sm text-muted-foreground">
The Python packages your function nodes can import. They are installed
into an environment of their own, separate from the engine's, so a
version you pin here is the one your code gets. The list is kept with
your flows, so a rebuilt deployment installs the same set again.
{adopted
? "The Python packages your function nodes can import. Fluksio was installed into an environment you already had, so that is the one your nodes run on — and it is yours to change, with pip or uv rather than from here."
: "The Python packages your function nodes can import. They are installed into an environment of their own, separate from the engine's, so a version you pin here is the one your code gets. The list is kept with your flows, so a rebuilt deployment installs the same set again."}
</p>
</div>
<section className="grid gap-3">
<h2 className={SECTION}>Requirements</h2>
<textarea
className="min-h-[160px] w-full rounded-md border border-border bg-card p-3 font-mono text-sm shadow-e1 outline-none focus-visible:border-primary"
spellCheck={false}
aria-label="Requirements"
data-testid="requirements"
placeholder={"requests==2.32.3\npandas>=2.2"}
value={requirements}
onChange={(event) => setDraft(event.target.value)}
/>
<div className="flex items-center gap-3">
<Button
disabled={apply.isPending}
data-testid="apply-modules"
onClick={() => apply.mutate(requirements)}
>
<Package />
{apply.isPending ? "Installing" : "Apply"}
</Button>
<span className="text-sm text-muted-foreground">
{dirty
? "Not applied yet."
: data?.applied
? "Installed and in step."
: "What is installed does not match this list."}
</span>
</div>
{output ? (
<pre
className="max-h-64 overflow-auto rounded-md border border-border bg-card p-3 font-mono text-xs shadow-e1"
data-testid="modules-output"
>
{output}
</pre>
) : null}
</section>
{adopted ? null : (
<section className="grid gap-3">
<h2 className={SECTION}>Requirements</h2>
<textarea
className="min-h-[160px] w-full rounded-md border border-border bg-card p-3 font-mono text-sm shadow-e1 outline-none focus-visible:border-primary"
spellCheck={false}
aria-label="Requirements"
data-testid="requirements"
placeholder={"requests==2.32.3\npandas>=2.2"}
value={requirements}
onChange={(event) => setDraft(event.target.value)}
/>
<div className="flex items-center gap-3">
<Button
disabled={apply.isPending}
data-testid="apply-modules"
onClick={() => apply.mutate(requirements)}
>
<Package />
{apply.isPending ? "Installing" : "Apply"}
</Button>
<span className="text-sm text-muted-foreground">
{dirty
? "Not applied yet."
: data?.applied
? "Installed and in step."
: "What is installed does not match this list."}
</span>
</div>
{output ? (
<pre
className="max-h-64 overflow-auto rounded-md border border-border bg-card p-3 font-mono text-xs shadow-e1"
data-testid="modules-output"
>
{output}
</pre>
) : null}
</section>
)}
<section className="grid gap-3">
<h2 className={SECTION}>Installed</h2>
<p className="text-sm text-muted-foreground">
<p className="text-sm text-muted-foreground" data-testid="modules-venv">
{data
? `Python ${data.python_version || "unknown"} at ${data.venv_path}`
: "Reading the environment…"}
</p>
{adopted ? (
<p
className="text-sm text-muted-foreground"
data-testid="modules-adopted"
>
Adopted this is the environment Fluksio itself is installed in.
Add a package the way you added the rest:{" "}
<span className="font-mono">pip install </span>, then sync or
restart so the workers pick it up. For a venv the engine owns
instead, set <span className="font-mono">NODE_VENV=managed</span>.
</p>
) : null}
{packages.length === 0 ? (
<p className="text-sm text-muted-foreground">
Nothing installed yet node code has the standard library.