diff --git a/README.md b/README.md index 60ae800..6f28a2c 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Choose your platform below. The scripts include both **Safe Mode** (asks for con All images support both **AMD64** (x86_64) and **ARM64** (Apple Silicon, etc.) architectures. All functions support switching between Docker image variants using flags: -- **No flag** - Base image (Node.js, Git, basic tools) +- **No flag** - Base image (Node.js, Python with pip & pipx, Git, basic tools) - **`--dotnet`** (`-d`) - .NET image (includes .NET 8, 9 & 10 SDKs) - **`--dotnet8`** (`-d8`) - .NET 8 image (includes .NET 8 SDK) - **`--dotnet9`** (`-d9`) - .NET 9 image (includes .NET 9 SDK) @@ -669,7 +669,7 @@ This project provides multiple Docker image variants for different development s | Tag | Flag | Description | |-----|------|-------------| -| `latest` | *(default)* | Base image with Node.js 20, Git, and essential tools | +| `latest` | *(default)* | Base image with Node.js 20, Python (pip & pipx), Git, and essential tools | | `dotnet` | `--dotnet` | .NET 8, 9 & 10 SDKs | | `dotnet-8` | `--dotnet8` | .NET 8 SDK only | | `dotnet-9` | `--dotnet9` | .NET 9 SDK only | diff --git a/docker/generated/Dockerfile.default b/docker/generated/Dockerfile.default index 023f4aa..d972272 100644 --- a/docker/generated/Dockerfile.default +++ b/docker/generated/Dockerfile.default @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.dotnet b/docker/generated/Dockerfile.dotnet index 11bfba2..8a1756c 100644 --- a/docker/generated/Dockerfile.dotnet +++ b/docker/generated/Dockerfile.dotnet @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.dotnet-10 b/docker/generated/Dockerfile.dotnet-10 index b202b4f..744e08f 100644 --- a/docker/generated/Dockerfile.dotnet-10 +++ b/docker/generated/Dockerfile.dotnet-10 @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.dotnet-8 b/docker/generated/Dockerfile.dotnet-8 index c332b5b..677e39e 100644 --- a/docker/generated/Dockerfile.dotnet-8 +++ b/docker/generated/Dockerfile.dotnet-8 @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.dotnet-9 b/docker/generated/Dockerfile.dotnet-9 index fb1a75b..1ebb7dc 100644 --- a/docker/generated/Dockerfile.dotnet-9 +++ b/docker/generated/Dockerfile.dotnet-9 @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.dotnet-playwright b/docker/generated/Dockerfile.dotnet-playwright index c31907d..40a0ecd 100644 --- a/docker/generated/Dockerfile.dotnet-playwright +++ b/docker/generated/Dockerfile.dotnet-playwright @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.dotnet-rust b/docker/generated/Dockerfile.dotnet-rust index 83f05d4..9d9ab21 100644 --- a/docker/generated/Dockerfile.dotnet-rust +++ b/docker/generated/Dockerfile.dotnet-rust @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.golang b/docker/generated/Dockerfile.golang index 812a9f0..81c885f 100644 --- a/docker/generated/Dockerfile.golang +++ b/docker/generated/Dockerfile.golang @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.java b/docker/generated/Dockerfile.java index b252e8e..5a39ca2 100644 --- a/docker/generated/Dockerfile.java +++ b/docker/generated/Dockerfile.java @@ -10,6 +10,8 @@ ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.playwright b/docker/generated/Dockerfile.playwright index 37ff6e8..ee8ffc2 100644 --- a/docker/generated/Dockerfile.playwright +++ b/docker/generated/Dockerfile.playwright @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/generated/Dockerfile.rust b/docker/generated/Dockerfile.rust index d133e5d..8349a89 100644 --- a/docker/generated/Dockerfile.rust +++ b/docker/generated/Dockerfile.rust @@ -10,6 +10,8 @@ FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -17,12 +19,30 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh + # --- snippet: docker-cli --- # Install Docker CLI to support brokered Docker socket scenarios. # Only the CLI is installed; the daemon is the host's, mediated by copilot_here's broker. diff --git a/docker/snippets/system-packages.Dockerfile b/docker/snippets/system-packages.Dockerfile index 8d88e75..7856a09 100644 --- a/docker/snippets/system-packages.Dockerfile +++ b/docker/snippets/system-packages.Dockerfile @@ -2,6 +2,8 @@ ENV DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, gosu, nano, xdg-utils, zsh, and related utilities for the entrypoint script and testing. +# Python tooling (pip + venv + pipx) ships here so every image can install pip-distributed +# CLIs; Debian 12 enforces PEP 668, so `pipx install ` is the supported path. RUN apt-get update && apt-get install -y \ apt-transport-https \ curl \ @@ -9,8 +11,26 @@ RUN apt-get update && apt-get install -y \ gosu \ gpg \ nano \ + pipx \ + python3 \ + python3-pip \ + python3-venv \ software-properties-common \ wget \ xdg-utils \ zsh \ && rm -rf /var/lib/apt/lists/* + +# pipx installs apps into the runtime user's ~/.local/bin, so that dir needs to be on PATH +# for pipx-installed CLIs (e.g. `pipx install apm`) to be reachable without a per-session +# `pipx ensurepath`. The entrypoint always runs as appuser with HOME=/home/appuser. +# Append (not prepend): the entrypoint runs as root and resolves tools like getent/groupadd/ +# node via PATH before dropping to appuser, so a system binary must always win over anything +# in the user-writable (and possibly bind-mounted) ~/.local/bin — otherwise a planted binary +# there could execute as root. Appending keeps pipx apps reachable while system paths stay +# authoritative; pipx app names don't collide with system binaries. +# ENV covers the exec'd command and non-login shells; the profile.d drop-in re-adds it for +# login shells, which otherwise reset PATH via /etc/profile (Debian zsh sources it too). +ENV PATH="${PATH}:/home/appuser/.local/bin" +RUN echo 'case ":${PATH}:" in *:/home/appuser/.local/bin:*) ;; *) PATH="${PATH:+${PATH}:}/home/appuser/.local/bin" ;; esac' \ + > /etc/profile.d/copilot-local-bin.sh diff --git a/docs/docker-images.md b/docs/docker-images.md index 0daa6d3..9e06457 100644 --- a/docs/docker-images.md +++ b/docs/docker-images.md @@ -7,6 +7,7 @@ This repository publishes multiple Docker image variants to optimize for size an The standard copilot_here image with: - Node.js 20 +- Python 3 with pip & pipx (Debian 12 enforces PEP 668, so `pipx install ` is the supported path for pip-distributed CLIs) - GitHub Copilot CLI - Git, curl, gpg, gosu