If you have deployed a Python web application in the last five years, you have probably written something like this in your Dockerfile:
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
It works. It is also wrong. You are installing dependencies first, which is correct for Docker layer caching, but you are separating the dependency list from the project metadata manually. If your requirements.txt drifts from what pyproject.toml actually declares, your Docker build succeeds and your application fails at runtime. You find out when the container starts, not when you build it.
A feature landing in pip 26.2, scheduled for the end of July 2026, fixes this at the source. The new pip install --only-deps flag installs all runtime dependencies for a package without installing the package itself. It sounds small. It is the kind of small that eliminates an entire class of deployment bugs.
What It Does
The flag does exactly what it says. Given a Python project with a pyproject.toml:
pip install --only-deps .
This reads the dependency declarations from your project metadata, installs every runtime dependency, and stops. Your package is not installed. Build dependencies like Cython are not included — only what your application needs to run.
Until now, getting this behavior required one of several workarounds, none of them good. You could extract the dependency list by hand with pip freeze or a parsing script and feed it to pip install -r. You could build the entire package just to get the dependencies. You could use a third-party tool like uv sync --no-install-project, which works but means adopting a different package manager for one feature that pip should have had years ago.
Developer James O’Claire, who tracked the history of this feature request, found examples stretching back years. The workarounds were well-documented. The frustration was well-documented. The fix is finally landing.
The Three Workarounds People Use Today (and Why They All Suck)
Before --only-deps, if you wanted to separate dependency installation from package installation in an automated pipeline, you had three options. Here they are, along with the failure mode for each.
Option 1: Maintain a separate requirements.txt. You export your dependencies with pip freeze or pip-compile and commit the resulting file. Your Dockerfile copies requirements.txt first, installs from it, then copies the source. The problem is drift. Someone adds httpx to pyproject.toml and forgets to regenerate requirements.txt. Docker builds cache the old layer. The application fails at runtime with an ImportError. Pre-commit hooks that auto-regenerate the file help, but hooks get skipped with --no-verify, disabled during rebases, and quietly break when the generation tool updates its format.
Option 2: Install the package twice. You copy everything, run pip install . to get dependencies, then install again at the end. This defeats Docker layer caching entirely because the first COPY includes your source code, so every code change invalidates the dependency cache. Your builds are slow and you are reinstalling numpy from source on every push for no reason.
Option 3: Use a third-party tool. uv sync --no-install-project does exactly what --only-deps will do, and it does it faster. Poetry’s poetry install --no-root is equivalent. But adopting a new package manager for one feature means your entire team has to switch, your CI system needs the new tool installed, and every tutorial and Stack Overflow answer that assumes pip becomes slightly less applicable. For teams that have already adopted uv, this is not a problem. For teams that have not, it is a lot of friction for a single flag.
--only-deps makes Option 4 the obvious choice: use pip, the tool you already have, the way it should have worked all along.
Before and After: The Docker Build That Actually Works
Here is what a Dockerfile for a FastAPI application looks like today, using the requirements.txt workaround:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN pip install --no-cache-dir --no-deps .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
And here is what it looks like with --only-deps, after pip 26.2:
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir --only-deps .
COPY . .
RUN pip install --no-cache-dir --no-deps .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
The difference is one file. Instead of copying a generated requirements.txt that may be stale, you copy the single source of truth — pyproject.toml. If a dependency changes in pyproject.toml, the cache invalidates and Docker reinstalls. If only application code changes, Docker uses the cached layer. No drift. No stale files. No pre-commit hooks to maintain.
For multi-stage builds, the pattern extends cleanly. Your build stage installs build dependencies and compiles wheels. Your runtime stage copies only the wheels and installs them with --only-deps, keeping the final image small:
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml .
RUN pip install --only-deps --target /deps .
COPY . .
RUN pip install --no-deps --target /deps .
FROM python:3.12-slim
COPY --from=builder /deps /deps
ENV PYTHONPATH=/deps
CMD ["python", "-m", "app.main"]
CI Pipelines That Do Not Reinstall the World
The same logic applies to GitHub Actions and similar CI systems. A typical Python CI job installs dependencies on every run. With caching, you can avoid the network round-trip, but pip still verifies every package. With --only-deps and a cache key based on your lock file or pyproject.toml hash, the CI system skips installation entirely when dependencies have not changed:
- uses: actions/cache@v4
id: cache
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: pip install --only-deps .
- name: Install project
run: pip install --no-deps .
- name: Run tests
run: pytest
The if: steps.cache.outputs.cache-hit != 'true' guard means the dependency installation step only runs when pyproject.toml changes. On a typical PR that only touches application code, CI starts running tests in seconds instead of minutes.
The benefits compound in monorepos. If you have five Python services sharing a CI pipeline, each one with slightly different dependencies, --only-deps lets you install only what each service needs without maintaining five separate requirements files. Each service declares its dependencies in its own pyproject.toml. The CI system reads from the source of truth. Nothing drifts.
What Else Is Coming in pip 26.2
The --only-deps flag is not the only improvement landing in this release. pip 26.2 also includes more robust error messages when dependency resolution fails — instead of a cryptic wall of version conflicts, you get a structured explanation of which package requires which version and where the conflict originated. This has been available in uv for a while and was one of the most-requested pip features after dependency resolution performance.
The trend is worth noting. pip is not trying to compete with uv on speed. It cannot — uv is written in Rust and pip is written in Python. But on correctness, usability, and feature completeness, pip is closing the gaps that matter to everyday development workflows. --only-deps is the kind of feature that makes the default tool good enough that switching to a faster alternative becomes a performance optimization rather than a necessity.
How Teams Actually Adopt This
The feature lands at the end of July 2026. Most teams will not adopt it immediately because their CI images and developer machines will take weeks or months to pick up the new pip version. Python 3.14 ships with pip 26.2, but anyone pinned to an older Python version will need to upgrade pip explicitly with pip install --upgrade pip.
If your team uses Docker, the transition path is straightforward. Update your base image to one that includes Python 3.14, or add RUN pip install --upgrade pip as the first line of your Dockerfile. Once pip 26.2 is available, replace the COPY requirements.txt pattern with COPY pyproject.toml and --only-deps. Test it on a staging deployment before rolling it to production. The change is small enough that the risk is low, but any change to how dependencies are installed deserves a staging run.
If your team uses a requirements.txt workflow with pip-tools or pip-compile, the shift is larger. You are moving from a generated file that you audit to a declarative file that you maintain. The audit step disappears. The tradeoff is that pyproject.toml becomes the sole dependency specification, and any pinning logic that lived in the requirements generation process needs to move into the pyproject.toml itself or into a lock file that pip understands. This is a workflow change, not just a flag change. Budget time for the team discussion, not just the code change.
The teams that benefit most are the ones still using raw pip without a wrapper tool. Those teams have been dealing with the requirements.txt drift problem for years. --only-deps solves it with a flag instead of a migration.
When Not to Use It
--only-deps skips build dependencies. If your package needs Cython, cmake, or a Rust compiler to build from source, those will not be installed. For web applications that install from pre-built wheels — which is most of them — this does not matter. For packages that compile C extensions at install time, it does. Read the distinction carefully before adopting this in a build pipeline that mixes pure-Python and compiled dependencies.
The flag also does not install the package itself. This is obvious from the name but easy to forget when you are writing a Dockerfile at 11 PM. Your application will not run if you only run pip install --only-deps and then try to start it. You still need a separate pip install step for the package, typically with --no-deps to avoid reinstalling everything.
One subtlety worth flagging: --only-deps reads from pyproject.toml in the current directory. If your project uses a src layout where the package lives in a subdirectory, make sure you are running the command from the project root where pyproject.toml lives. Running it from inside the src directory will not find the metadata and will fail with an error that does not immediately explain why. This is not a bug. It is the expected behavior of a tool that looks for project metadata in the current working directory. It will trip people up anyway.
The Bigger Picture
Python packaging has been in a state of productive chaos for the past three years. uv challenged pip on speed. Rye and Poetry challenged the entire workflow model. PEP 735 standardized dependency groups, making requirements.txt files for dev dependencies officially unnecessary. pyproject.toml became the universal standard.
--only-deps fills one of the remaining gaps in pip’s feature set: the ability to treat project metadata as the authoritative dependency source in automated workflows without installing the project as a side effect. It is a single flag. It fixes a single problem. But the problem it fixes is one that every Python web developer who builds Docker images or maintains CI pipelines has tripped over at least once.
The feature lands at the end of July 2026. When it does, delete your requirements.txt regeneration scripts. They were a workaround. The workaround is obsolete.
Discussion
Leave a comment
No comments yet
Be the first to start the conversation.