diff --git a/docker/Dockerfile.console b/docker/Dockerfile.console index c90685c2..3150907e 100644 --- a/docker/Dockerfile.console +++ b/docker/Dockerfile.console @@ -20,11 +20,23 @@ RUN /usr/local/cargo/bin/flmadm install \ --skip-build \ --force +# Install Python 3.12 via uv to match runtime (ubuntu:24.04) +# Then pre-warm uv cache for flamepy dependencies in the builder +# This avoids network access during runtime container startup +RUN /usr/bin/uv python install 3.12 && \ + UV_CACHE_DIR=/usr/local/flame/data/cache/uv \ + UV_LINK_MODE=copy \ + /usr/bin/uv run --python 3.12 \ + --find-links /usr/local/flame/wheels \ + --with pip \ + --with flamepy \ + python -c "import flamepy; print('flamepy cache warmed in builder')" + FROM ubuntu:24.04 RUN apt-get update && apt-get install -y wget vim iputils-ping ssh curl python3 python3-pip && rm -rf /var/lib/apt/lists/* -# Copy the entire installation from builder (including uv in bin/) +# Copy the entire installation from builder (including uv in bin/ and pre-warmed cache) COPY --from=builder /usr/local/flame /usr/local/flame # Add flame binaries to PATH (includes uv) @@ -32,6 +44,7 @@ ENV PATH="/usr/local/flame/bin:${PATH}" ENV FLAME_HOME=/usr/local/flame # Install flamepy and pytest for test client using uv from FLAME_HOME -RUN /usr/local/flame/bin/uv pip install --system --break-system-packages --no-cache /usr/local/flame/sdk/python pytest +# Use UV_LINK_MODE=copy for cross-filesystem compatibility +RUN UV_LINK_MODE=copy /usr/local/flame/bin/uv pip install --system --break-system-packages --no-cache /usr/local/flame/sdk/python pytest CMD ["service", "ssh", "start", "-D"] diff --git a/docker/Dockerfile.fem b/docker/Dockerfile.fem index b23a6e84..36c06ccd 100644 --- a/docker/Dockerfile.fem +++ b/docker/Dockerfile.fem @@ -21,13 +21,25 @@ RUN /usr/local/cargo/bin/flmadm install \ --skip-build \ --force +# Install Python 3.12 via uv to match runtime (ubuntu:24.04) +# Then pre-warm uv cache for flamepy dependencies in the builder +# This avoids network access during runtime container startup +RUN /usr/bin/uv python install 3.12 && \ + UV_CACHE_DIR=/usr/local/flame/data/cache/uv \ + UV_LINK_MODE=copy \ + /usr/bin/uv run --python 3.12 \ + --find-links /usr/local/flame/wheels \ + --with pip \ + --with flamepy \ + python -c "import flamepy; print('flamepy cache warmed in builder')" + FROM ubuntu:24.04 RUN apt-get update && apt-get install -y python3-pip git && rm -rf /var/lib/apt/lists/* WORKDIR /usr/local/flame/work -# Copy the entire installation from builder (including uv in bin/) +# Copy the entire installation from builder (including uv in bin/ and pre-warmed cache) COPY --from=builder /usr/local/flame /usr/local/flame ENV FLAME_HOME=/usr/local/flame diff --git a/executor_manager/src/shims/host_shim.rs b/executor_manager/src/shims/host_shim.rs index e21377db..84b4c8d9 100644 --- a/executor_manager/src/shims/host_shim.rs +++ b/executor_manager/src/shims/host_shim.rs @@ -272,9 +272,14 @@ impl HostShim { }; let work_dir = cur_dir.clone(); - // Setup working directory and get environment overrides + // Setup working directory and get environment defaults + // Use entry().or_insert() so application-specific envs take precedence over defaults + // This allows applications like flmrun to specify UV_CACHE_DIR pointing to + // the pre-cached directory instead of using a per-instance empty cache let wd_envs = Self::setup_working_directory(&work_dir)?; - envs.extend(wd_envs); + for (key, value) in wd_envs { + envs.entry(key).or_insert(value); + } let log_out = OpenOptions::new() .create(true) diff --git a/flmadm/src/managers/installation.rs b/flmadm/src/managers/installation.rs index 115dd9e1..78f01311 100644 --- a/flmadm/src/managers/installation.rs +++ b/flmadm/src/managers/installation.rs @@ -242,10 +242,11 @@ impl InstallationManager { let uv_cache_dir = paths.cache.join("uv"); - // Install flamepy and dependencies to populate the cache - // Using --target to a temp directory avoids needing a venv + // Phase 1: Cache flamepy and all its dependencies (grpcio, protobuf, cloudpickle, etc.) + // Using --target to a temp directory to populate the cache without needing a venv let cache_target = paths.work.join(".flamepy-cache-target"); - fs::create_dir_all(&cache_target).context("Failed to create temporary directory for caching")?; + fs::create_dir_all(&cache_target) + .context("Failed to create temporary directory for caching")?; let install_output = std::process::Command::new(&uv_path) .arg("pip") @@ -255,7 +256,7 @@ impl InstallationManager { .arg("--find-links") .arg(&paths.wheels) .arg("flamepy") - .arg("pip") // Also cache pip as it's used by flmrun + .arg("pip") // Also cache pip as it's used by flmrun for user packages .env("UV_CACHE_DIR", &uv_cache_dir) .output() .context("Failed to execute uv pip install")?; @@ -271,7 +272,41 @@ impl InstallationManager { stderr.lines().next().unwrap_or(&stderr) ); } else { - println!(" ✓ Cached dependencies to: {}", uv_cache_dir.display()); + println!( + " ✓ Cached flamepy and dependencies to: {}", + uv_cache_dir.display() + ); + } + + // Phase 2: Pre-warm uv's ephemeral environment cache by running the exact command + // that flmrun uses at startup. This ensures all packages are cached in the format + // that 'uv run --with' expects (which differs from 'uv pip install'). + println!(" 🔄 Pre-warming uv run cache..."); + + // Create a simple Python script that just exits successfully + let run_output = std::process::Command::new(&uv_path) + .arg("run") + .arg("--find-links") + .arg(&paths.wheels) + .arg("--with") + .arg("pip") + .arg("--with") + .arg("flamepy") + .arg("python") + .arg("-c") + .arg("import sys; sys.exit(0)") + .env("UV_CACHE_DIR", &uv_cache_dir) + .output() + .context("Failed to execute uv run warmup")?; + + if !run_output.status.success() { + let stderr = String::from_utf8_lossy(&run_output.stderr); + println!( + " ⚠️ Failed to pre-warm uv run cache (will warm at runtime): {}", + stderr.lines().next().unwrap_or(&stderr) + ); + } else { + println!(" ✓ Pre-warmed uv run cache"); } Ok(())