Skip to content

Commit

Permalink
Stop including pip in the final app image (#264)
Browse files Browse the repository at this point in the history
After #254, pip is now installed into its own layer rather than into the
system site-packages directory inside the Python layer.

This means its now possible to exclude pip from the final app image, by
making the pip layer be a build-only layer.

Excluding pip from the final app image:
- Prevents several classes of user error/confusion/bad app design
  patterns seen in support tickets (see #255 for more details).
- Reduces app image supply chain surface area.
- Reduces app image size by 13 MB and layer count by 1, meaning less
  to have to push to the remote registry.
- Matches the approach used for Poetry, where we don't make Poetry
  available at run-time either.

Users that need pip at run-time for a temporary debugging task can run
`python -m ensurepip --default-pip` in the container at run-time to make
it available again (this command doesn't even have to download anything
- it uses the pip bundled with Python).

Or if pip is an actual run-time dependency of the app, then the app can
add `pip` to its `requirements.txt` (which much more clearly conveys the
requirements of the app, and also allows the app to pick what pip
version it needs at run-time).

Should we find that pip's absence causes confusion in the future, we
could always add a wrapper/shim `pip` script in the app image which does
something like:

```
echo "pip isn't installed at run-time, if you need it temporarily run 'python -m ensurepip --default-pip' to install it"
exit 1
```

...to improve discoverability.

We'll also document pip (and Poetry) being available at build-time only
in the docs that will be added by #11.

Closes #255.
GUS-W-16697386.
  • Loading branch information
edmorley authored Sep 9, 2024
1 parent d8fa779 commit 05aa01e
Show file tree
Hide file tree
Showing 3 changed files with 12 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- pip is now only available during the build, and is longer included in the final app image. ([#264](https://github.com/heroku/buildpacks-python/pull/264))

## [0.17.1] - 2024-09-07

### Changed
Expand Down
6 changes: 3 additions & 3 deletions src/layers/pip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub(crate) fn install_pip(
layer_name!("pip"),
CachedLayerDefinition {
build: true,
launch: true,
launch: false,
invalid_metadata_action: &|_| InvalidMetadataAction::DeleteLayer,
restored_layer_action: &|cached_metadata: &PipLayerMetadata, _| {
let cached_pip_version = cached_metadata.pip_version.clone();
Expand All @@ -49,15 +49,15 @@ pub(crate) fn install_pip(
// reduce build log spam and prevent users from thinking they need to manually upgrade.
// https://pip.pypa.io/en/stable/cli/pip/#cmdoption-disable-pip-version-check
.chainable_insert(
Scope::All,
Scope::Build,
ModificationBehavior::Override,
"PIP_DISABLE_PIP_VERSION_CHECK",
"1",
)
// Move the Python user base directory to this layer instead of under HOME:
// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONUSERBASE
.chainable_insert(
Scope::All,
Scope::Build,
ModificationBehavior::Override,
"PYTHONUSERBASE",
layer.path(),
Expand Down
18 changes: 5 additions & 13 deletions tests/pip_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use libcnb_test::{assert_contains, assert_empty, BuildpackReference, PackResult,

#[test]
#[ignore = "integration test"]
#[allow(clippy::too_many_lines)]
fn pip_basic_install_and_cache_reuse() {
let mut config = default_build_config("tests/fixtures/pip_basic");
config.buildpacks(vec![
Expand Down Expand Up @@ -69,14 +68,13 @@ fn pip_basic_install_and_cache_reuse() {

// Check that at run-time:
// - The correct env vars are set.
// - pip is available (rather than just during the build).
// - Both pip and Python can find the typing-extensions package.
// - pip isn't available.
// - Python can find the typing-extensions package.
let command_output = context.run_shell_command(
indoc! {"
set -euo pipefail
printenv | sort | grep -vE '^(_|HOME|HOSTNAME|OLDPWD|PWD|SHLVL)='
echo
pip list
! command -v pip > /dev/null || { echo 'pip unexpectedly found!' && exit 1; }
python -c 'import typing_extensions'
"}
);
Expand All @@ -85,18 +83,12 @@ fn pip_basic_install_and_cache_reuse() {
command_output.stdout,
formatdoc! {"
LANG=C.UTF-8
LD_LIBRARY_PATH=/layers/heroku_python/venv/lib:/layers/heroku_python/python/lib:/layers/heroku_python/pip/lib
PATH=/layers/heroku_python/venv/bin:/layers/heroku_python/python/bin:/layers/heroku_python/pip/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PIP_DISABLE_PIP_VERSION_CHECK=1
LD_LIBRARY_PATH=/layers/heroku_python/venv/lib:/layers/heroku_python/python/lib
PATH=/layers/heroku_python/venv/bin:/layers/heroku_python/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PIP_PYTHON=/layers/heroku_python/venv
PYTHONHOME=/layers/heroku_python/python
PYTHONUNBUFFERED=1
PYTHONUSERBASE=/layers/heroku_python/pip
VIRTUAL_ENV=/layers/heroku_python/venv
Package Version
----------------- -------
typing_extensions 4.12.2
"}
);

Expand Down

0 comments on commit 05aa01e

Please sign in to comment.