Repository navigation
feat: install pip deps constrained to conda's resolved versions - #61
Merged
Merged
Conversation
When an environment.yaml mixes conda dependencies with a pip: section, `micromamba create -f env.yaml` hands the pip subsection to a plain `pip install` with no constraints. pip has no knowledge of conda's pins, so a pip package (e.g. torch) that requires `numpy>=2` will silently upgrade the conda-installed numpy past its pin, leaving an environment that violates the conda spec. This is inherent to composing two independent resolvers over one prefix; neither conda nor pip treats the other's record as a constraint. Fix it in the layer that sees both: split the pip section out of the conda solve and install it in a second phase constrained to the versions conda just resolved (`pip install -c <frozen conda env>`). A conflict now fails the build (pip resolution error) instead of clobbering a pin, and a trailing `pip check` confirms the final tree is consistent. - templates.py: _split_conda_pip() separates conda vs pip, injects `pip` into the conda deps when needed, and exposes conda_env_yaml / pip_requirements / has_pip to the templates. - conda_on_base / builder_stage / single_stage fragments: two-phase install. Envs with no pip section render exactly as before. - docs + tests. R (conda + renv) has the same class of problem via a different mechanism and is intentionally left for a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
johnyaku
added a commit
that referenced
this pull request
Jun 17, 2026
* fix: don't rm root-owned COPY temp files as non-root build user The builder/single-stage paths run as $MAMBA_USER, but COPY creates /tmp/requirements.txt owned by root. In sticky /tmp a non-owner can't delete it, so `rm -f /tmp/requirements.txt` failed with "Operation not permitted" and broke the build (CI #61). Drop the cleanup in these two paths: the builder stage is discarded (multi-stage), and single-stage already leaves /tmp/env.yaml behind, so this is consistent and the leftover is a harmless few-KB text file. The conda_on_base path runs as root and keeps its cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump version to 0.2.7 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When an
environment.yamlmixes conda dependencies with apip:section,micromamba create -f env.yamlhands the pip subsection to a plainpip installwith no constraints. pip doesn't know conda's pins exist, so a pip package (e.g.torchrequiringnumpy>=2) will silently upgrade the conda-installednumpypast its pin — leaving an environment that violates the conda spec, sometimes with ABI breakage.This is inherent to composing two independent resolvers over one prefix: conda records its solve in
conda-meta/, pip in*.dist-info, and neither treats the other's record as a constraint. conda's own docs flag pip-after-conda as a known hazard. Docker didn't cause it (same thing happens with a plainconda env create), but our build is the one place that sees both halves — so that's where the coordination belongs.Fix
Split the
pip:section out of the conda solve and install it in a second phase constrained to the versions conda just resolved:RUN micromamba create -y -n env -f /tmp/env.yaml && micromamba run -n env python -m pip list --format=freeze > /tmp/conda.constraints.txt && micromamba run -n env python -m pip install --no-cache-dir --constraint /tmp/conda.constraints.txt --requirement /tmp/requirements.txt && micromamba run -n env python -m pip check && ...pip checkconfirms the final dependency tree is consistent.Details
templates.py:_split_conda_pip()separates conda vs pip deps, injectspipinto the conda deps when the user didn't list it (so phase 2 can run), and preserves pip option lines (--extra-index-url,git+URLs). New template context:conda_env_yaml,pip_requirements,has_pip.env_yaml(full) is retained for custom templates.conda_on_base.j2,builder_stage.j2,single_stage.j2: two-phase install (pip step inserted beforeconda-packin the packed paths).pip:section, the rendered Dockerfile is identical to before (verified by test).Testing
pytest: 116 passed, 2 skipped (Docker smoke tests).ruff check+ format clean._split_conda_pip(separation, pip injection, no-duplicate, no-pip passthrough) and rendered output (constrained two-phase ordering; no-pip omits the pip steps).--baseDockerfile by hand to confirm output.Scope / follow-up
conda+renv) has the same class of problem via a different mechanism (renv shadows conda's R library via.libPaths(); renv is exact-pinned so the conflict is lockfile-vs-conda, not a runaway resolver). The constraints trick doesn't transfer — the renv analog is conflict detection plus "keep R single-managed". Intentionally deferred; docs note this.🤖 Generated with Claude Code