From eba7c91ff8c831940ad2803f641a21a75d6a7b39 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 11:39:25 -0700 Subject: [PATCH 1/8] feat: replace kubedock with native container-in-container support Switch the Dev Spaces image from kubedock-based container proxying to native rootless podman via user namespaces (OCP 4.17+ / Dev Spaces 3.20). - Remove kubedock podman.py wrapper - Add entrypoint.sh for dynamic UID/subuid/subgid mapping - Add pod-overrides and container-overrides to devfile for user namespace support (hostUsers: false, procMount: Unmasked, /dev/fuse and /dev/net/tun device access) - Add buildah, skopeo, and crun packages - Set BUILDAH_ISOLATION=chroot for nested user namespace compatibility - Set newuidmap/newgidmap capabilities for rootless podman - Update Python from 3.11 to 3.12 - Add python3/pip3 alternatives for version-agnostic invocation - Add colored bash prompt modeled after Fedora's bash-color-prompt - Fix hardcoded python3.11 path in ansible-navigator devfile command - Bump oc client from 4.15 to 4.17 Made-with: Cursor --- devfile.yaml | 14 ++++++- devspaces/Containerfile | 12 ++++-- devspaces/context/ansible-prompt.sh | 60 +++++++++++++++++++++++++++++ devspaces/context/entrypoint.sh | 24 ++++++++++++ devspaces/context/podman.py | 43 --------------------- devspaces/context/setup.sh | 25 ++++++++---- tools/setup-image.sh | 2 +- 7 files changed, 124 insertions(+), 56 deletions(-) create mode 100644 devspaces/context/ansible-prompt.sh create mode 100644 devspaces/context/entrypoint.sh delete mode 100755 devspaces/context/podman.py diff --git a/devfile.yaml b/devfile.yaml index f5139123..1725d84c 100644 --- a/devfile.yaml +++ b/devfile.yaml @@ -3,13 +3,22 @@ metadata: name: ansible-demo components: - name: tooling-container + attributes: + pod-overrides: + metadata: + annotations: + io.kubernetes.cri-o.Devices: "/dev/fuse,/dev/net/tun" + spec: + hostUsers: false + container-overrides: + securityContext: + procMount: Unmasked container: image: ghcr.io/ansible/ansible-devspaces:latest memoryRequest: 256M memoryLimit: 6Gi cpuRequest: 250m cpuLimit: 2000m - args: ["tail", "-f", "/dev/null"] env: - name: "ANSIBLE_COLLECTIONS_PATH" value: "~/.ansible/collections:/usr/share/ansible/collections:/projects/ansible-devspaces-demo/collections" @@ -81,7 +90,8 @@ commands: if [ ! -d "$HOME/.cache/ansible-navigator" ]; then mkdir -p "$HOME/.cache/ansible-navigator" fi - cp /usr/local/lib/python3.11/site-packages/ansible_navigator/data/catalog_collections.py $HOME/.cache/ansible-navigator + NAVIGATOR_DATA=$(python3 -c "import ansible_navigator.data; import pathlib; print(pathlib.Path(ansible_navigator.data.__file__).parent)") + cp "${NAVIGATOR_DATA}/catalog_collections.py" "$HOME/.cache/ansible-navigator" ansible-navigator --ee false workingDir: ${PROJECTS_ROOT}/ansible-devspaces-demo component: tooling-container diff --git a/devspaces/Containerfile b/devspaces/Containerfile index eb21666c..38c693c2 100644 --- a/devspaces/Containerfile +++ b/devspaces/Containerfile @@ -1,6 +1,6 @@ FROM quay.io/devfile/base-developer-image:ubi9-latest -ARG PYV=3.11 +ARG PYV=3.12 LABEL org.opencontainers.image.source=https://github.com/ansible/ansible-dev-tools LABEL org.opencontainers.image.authors="Ansible DevTools" @@ -11,8 +11,14 @@ LABEL org.opencontainers.image.description="An OpenShift Dev Spaces container im USER 0 WORKDIR /context -# install ansible-dev-tools specific packages and dependencies while avoiding -# adding multiple layers to the image. RUN --mount=type=bind,target=. --mount=type=cache,dst=/var/cache/dnf --mount=type=cache,dst=/root/.cache/pip ./setup.sh +ENV BUILDAH_ISOLATION=chroot + +COPY entrypoint.sh / +RUN chmod +x /entrypoint.sh + USER 10001 + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["tail", "-f", "/dev/null"] diff --git a/devspaces/context/ansible-prompt.sh b/devspaces/context/ansible-prompt.sh new file mode 100644 index 00000000..f898e000 --- /dev/null +++ b/devspaces/context/ansible-prompt.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Colored bash prompt for Ansible Dev Spaces, modeled after Fedora's +# bash-color-prompt (https://github.com/juhp/bash-color-prompt). +# Installed to /etc/profile.d/ for interactive login shells. +# cspell: ignore COLORTERM + +# Only apply to interactive bash sessions +[[ $- != *i* ]] && return + +# Respect NO_COLOR (https://no-color.org/) +if [[ -n "${NO_COLOR:-}" && -z "${BASH_PROMPT_USE_COLOR:-}" ]]; then + return +fi + +# Only activate on terminals that support color +case "${TERM:-}" in + *color* | xterm* | screen* | tmux* | linux) ;; + *) + [[ -z "${COLORTERM:-}" ]] && return + ;; +esac + +_adt_git_branch() { + local branch + branch=$(git symbolic-ref --short HEAD 2>/dev/null) || \ + branch=$(git rev-parse --short HEAD 2>/dev/null) + [[ -z "$branch" ]] && return + + local dirty + dirty=$(git status --porcelain --untracked-files=no --ignore-submodules=dirty 2>/dev/null | head -n1) + if [[ -n "$dirty" ]]; then + printf ' \001\e[33m\002(%s*)\001\e[0m\002' "$branch" + else + printf ' \001\e[32m\002(%s)\001\e[0m\002' "$branch" + fi +} + +_adt_build_prompt() { + local last_exit=$? + local red='\[\e[31m\]' + local green='\[\e[32m\]' + local blue='\[\e[34m\]' + local cyan='\[\e[36m\]' + local bold='\[\e[1m\]' + local reset='\[\e[0m\]' + + local prefix="" + if [[ -n "${container:-}" ]]; then + prefix="⬢ " + fi + + local status_indicator="" + if [[ $last_exit -ne 0 ]]; then + status_indicator="${red}[${last_exit}]${reset} " + fi + + PS1="${status_indicator}${prefix}${bold}${green}\u@\h${reset}:${bold}${blue}\w${reset}\$(_adt_git_branch)\$ " +} + +PROMPT_COMMAND="_adt_build_prompt" diff --git a/devspaces/context/entrypoint.sh b/devspaces/context/entrypoint.sh new file mode 100644 index 00000000..8a23b662 --- /dev/null +++ b/devspaces/context/entrypoint.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Entrypoint for the Ansible Dev Spaces container image. +# Sets up the dynamic UID mapping required for rootless podman +# with user namespaces (container-in-container without kubedock). +# cspell: ignore subuid subgid catatonit + +if [ ! -d "${HOME}" ]; then + mkdir -p "${HOME}" +fi + +if ! whoami &>/dev/null; then + if [ -w /etc/passwd ]; then + echo "${USER_NAME:-user}:x:$(id -u):0:${USER_NAME:-user} user:${HOME}:/bin/bash" >>/etc/passwd + echo "${USER_NAME:-user}:x:$(id -u):" >>/etc/group + fi +fi + +USER=$(whoami) +START_ID=$(( $(id -u) + 1 )) +END_ID=$(( 65536 - START_ID )) +echo "${USER}:${START_ID}:${END_ID}" >/etc/subuid +echo "${USER}:${START_ID}:${END_ID}" >/etc/subgid + +exec /usr/libexec/podman/catatonit -- "$@" diff --git a/devspaces/context/podman.py b/devspaces/context/podman.py deleted file mode 100755 index 1d13c5e2..00000000 --- a/devspaces/context/podman.py +++ /dev/null @@ -1,43 +0,0 @@ -#! /usr/bin/env python3 -"""A python wrapper to intercept podman calls and set CONTAINER_HOST to use kubedock.""" - -# cspell: ignore Unsetting -# ruff: noqa: T201 -from __future__ import annotations - -import os -import sys - - -ARGS = sys.argv -SUPPORTED = ( - "run", - "ps", - "exec", - "cp", - "logs", - "inspect", - "kill", - "rm", - "wait", - "stop", - "start", -) - -try: - ARGS.remove("--interactive") - ARGS.remove("--user=root") -except ValueError: - pass - -if ARGS[1] in SUPPORTED: - print("Setting CONTAINER_HOST to use kubedock") - os.environ["CONTAINER_HOST"] = "tcp://127.0.0.1:2475" -else: - print("Unsetting CONTAINER_HOST to use podman locally") - os.environ.pop("CONTAINER_HOST", None) - -ARGS[0] = "podman.orig" - -print(f"Executing: {' '.join(ARGS)}") -os.execvp(ARGS[0], ARGS) # noqa: S606 diff --git a/devspaces/context/setup.sh b/devspaces/context/setup.sh index cc702096..04d60316 100755 --- a/devspaces/context/setup.sh +++ b/devspaces/context/setup.sh @@ -1,5 +1,5 @@ #!/bin/bash -e -# cspell: ignore makecache overlayfs libssh chgrp noplugins +# cspell: ignore makecache overlayfs libssh chgrp noplugins newuidmap newgidmap subuid subgid set -eux pipefail DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) @@ -9,6 +9,8 @@ dnf install -y -q iptables-nft dnf -y -q makecache dnf -y -q update dnf install -y -q \ + buildah \ + crun \ dumb-init \ fuse-overlayfs \ gcc \ @@ -23,25 +25,34 @@ dnf install -y -q \ "python${PYV}-pip" \ "python${PYV}-pyyaml" \ "python${PYV}-wheel" \ + skopeo \ tar \ util-linux-user \ which \ zsh \ pinentry \ --exclude container-selinux -# python${PYV}-ruamel-yaml \ dnf -y -q clean all +# Set python3/pip3 alternatives so they work with or without version suffix +alternatives --install /usr/bin/python3 python3 "/usr/bin/python${PYV}" 100 +alternatives --install /usr/bin/pip3 pip3 "/usr/bin/pip${PYV}" 100 + "/usr/bin/python${PYV}" -m pip install --only-binary :all: --root-user-action=ignore "$(ls -1 ./*.whl)[server]" -r requirements.txt ansible-galaxy collection install -r requirements.yml -chgrp -R 0 /home && chmod -R g=u /etc/passwd /etc/group /home +# Setup for rootless podman with user namespaces (container-in-container) +setcap cap_setuid+ep /usr/bin/newuidmap +setcap cap_setgid+ep /usr/bin/newgidmap +touch /etc/subgid /etc/subuid +chown 0:0 /etc/subgid /etc/subuid + +chgrp -R 0 /home && chmod -R g=u /etc/passwd /etc/group /etc/subuid /etc/subgid /home -# Configure the podman wrapper -cp podman.py /usr/bin/podman.wrapper -chown 0:0 /usr/bin/podman.wrapper -chmod +x /usr/bin/podman.wrapper +# Install the colored bash prompt +cp ansible-prompt.sh /etc/profile.d/ansible-prompt.sh +chmod +r /etc/profile.d/ansible-prompt.sh # shellcheck disable=SC1091 source "$DIR/setup-image.sh" diff --git a/tools/setup-image.sh b/tools/setup-image.sh index ee3565db..1931797e 100755 --- a/tools/setup-image.sh +++ b/tools/setup-image.sh @@ -5,7 +5,7 @@ set -exuo pipefail # Install oc client -OC_VERSION=4.15 +OC_VERSION=4.17 curl -s -L "https://mirror.openshift.com/pub/openshift-v4/$(arch)/clients/ocp/stable-${OC_VERSION}/openshift-client-linux.tar.gz" | tar -C /usr/local/bin -xz --no-same-owner chmod +x /usr/local/bin/oc oc version --client=true From cb257c6f94babc557763e8465902e376220ad2d3 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 11:47:22 -0700 Subject: [PATCH 2/8] fix: address CI failures in lint and devspaces build - Add buildah, crun, skopeo, setcap, setgid to cspell dictionary - Remove unused cyan variable from ansible-prompt.sh (shellcheck SC2034) - Move entrypoint.sh install into setup.sh bind-mount RUN to avoid adding extra image layers (24 > 23 max) Made-with: Cursor --- .config/dictionary.txt | 5 +++++ devspaces/Containerfile | 3 --- devspaces/context/ansible-prompt.sh | 1 - devspaces/context/setup.sh | 4 ++++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.config/dictionary.txt b/.config/dictionary.txt index a0acd13b..44b423d5 100644 --- a/.config/dictionary.txt +++ b/.config/dictionary.txt @@ -10,9 +10,11 @@ antsibull arcname autoplay autouse +buildah capsys collectonly confest +crun devfile devspaces geckodriver @@ -26,7 +28,10 @@ pinentry prek pylibssh seccomp +setcap +setgid signingkey +skopeo unmarshal unmarshalling urandom diff --git a/devspaces/Containerfile b/devspaces/Containerfile index 38c693c2..df81c453 100644 --- a/devspaces/Containerfile +++ b/devspaces/Containerfile @@ -15,9 +15,6 @@ RUN --mount=type=bind,target=. --mount=type=cache,dst=/var/cache/dnf --mount=typ ENV BUILDAH_ISOLATION=chroot -COPY entrypoint.sh / -RUN chmod +x /entrypoint.sh - USER 10001 ENTRYPOINT ["/entrypoint.sh"] diff --git a/devspaces/context/ansible-prompt.sh b/devspaces/context/ansible-prompt.sh index f898e000..f702694c 100644 --- a/devspaces/context/ansible-prompt.sh +++ b/devspaces/context/ansible-prompt.sh @@ -40,7 +40,6 @@ _adt_build_prompt() { local red='\[\e[31m\]' local green='\[\e[32m\]' local blue='\[\e[34m\]' - local cyan='\[\e[36m\]' local bold='\[\e[1m\]' local reset='\[\e[0m\]' diff --git a/devspaces/context/setup.sh b/devspaces/context/setup.sh index 04d60316..eb69da0d 100755 --- a/devspaces/context/setup.sh +++ b/devspaces/context/setup.sh @@ -54,5 +54,9 @@ chgrp -R 0 /home && chmod -R g=u /etc/passwd /etc/group /etc/subuid /etc/subgid cp ansible-prompt.sh /etc/profile.d/ansible-prompt.sh chmod +r /etc/profile.d/ansible-prompt.sh +# Install the entrypoint for rootless podman UID mapping +cp entrypoint.sh /entrypoint.sh +chmod +x /entrypoint.sh + # shellcheck disable=SC1091 source "$DIR/setup-image.sh" From 5e100ea9a9f7406f1231da28d4d27e09a262ec31 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 11:49:12 -0700 Subject: [PATCH 3/8] fix: address Copilot review feedback - entrypoint.sh: fail fast with error if /etc/passwd is not writable and user cannot be resolved - entrypoint.sh: derive subordinate ID range from /proc/self/uid_map instead of assuming a fixed 65536 window, and validate the range is positive before writing subuid/subgid - setup.sh: follow alternatives --install with --set to guarantee python3/pip3 resolve deterministically - ansible-prompt.sh: preserve existing PROMPT_COMMAND hooks instead of unconditionally overwriting Made-with: Cursor --- devspaces/context/ansible-prompt.sh | 7 ++++++- devspaces/context/entrypoint.sh | 22 ++++++++++++++++++++-- devspaces/context/setup.sh | 2 ++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/devspaces/context/ansible-prompt.sh b/devspaces/context/ansible-prompt.sh index f702694c..ac52dd86 100644 --- a/devspaces/context/ansible-prompt.sh +++ b/devspaces/context/ansible-prompt.sh @@ -56,4 +56,9 @@ _adt_build_prompt() { PS1="${status_indicator}${prefix}${bold}${green}\u@\h${reset}:${bold}${blue}\w${reset}\$(_adt_git_branch)\$ " } -PROMPT_COMMAND="_adt_build_prompt" +# Preserve any existing PROMPT_COMMAND hooks +if [[ -n "${PROMPT_COMMAND:-}" ]]; then + PROMPT_COMMAND="_adt_build_prompt;${PROMPT_COMMAND}" +else + PROMPT_COMMAND="_adt_build_prompt" +fi diff --git a/devspaces/context/entrypoint.sh b/devspaces/context/entrypoint.sh index 8a23b662..cb7284a5 100644 --- a/devspaces/context/entrypoint.sh +++ b/devspaces/context/entrypoint.sh @@ -12,12 +12,30 @@ if ! whoami &>/dev/null; then if [ -w /etc/passwd ]; then echo "${USER_NAME:-user}:x:$(id -u):0:${USER_NAME:-user} user:${HOME}:/bin/bash" >>/etc/passwd echo "${USER_NAME:-user}:x:$(id -u):" >>/etc/group + else + echo "ERROR: Cannot resolve user and /etc/passwd is not writable" >&2 + exit 1 fi fi USER=$(whoami) -START_ID=$(( $(id -u) + 1 )) -END_ID=$(( 65536 - START_ID )) +CURRENT_UID=$(id -u) +START_ID=$(( CURRENT_UID + 1 )) + +# Derive the available UID/GID range from the namespace mapping +# rather than assuming a fixed 65536 window. +if [ -r /proc/self/uid_map ]; then + NAMESPACE_SIZE=$(awk '{print $3}' /proc/self/uid_map | head -n1) +else + NAMESPACE_SIZE=65536 +fi + +END_ID=$(( NAMESPACE_SIZE - START_ID )) +if [ "${END_ID}" -le 0 ]; then + echo "ERROR: No subordinate IDs available (uid=${CURRENT_UID}, namespace=${NAMESPACE_SIZE})" >&2 + exit 1 +fi + echo "${USER}:${START_ID}:${END_ID}" >/etc/subuid echo "${USER}:${START_ID}:${END_ID}" >/etc/subgid diff --git a/devspaces/context/setup.sh b/devspaces/context/setup.sh index eb69da0d..6ff3b508 100755 --- a/devspaces/context/setup.sh +++ b/devspaces/context/setup.sh @@ -36,7 +36,9 @@ dnf -y -q clean all # Set python3/pip3 alternatives so they work with or without version suffix alternatives --install /usr/bin/python3 python3 "/usr/bin/python${PYV}" 100 +alternatives --set python3 "/usr/bin/python${PYV}" alternatives --install /usr/bin/pip3 pip3 "/usr/bin/pip${PYV}" 100 +alternatives --set pip3 "/usr/bin/pip${PYV}" "/usr/bin/python${PYV}" -m pip install --only-binary :all: --root-user-action=ignore "$(ls -1 ./*.whl)[server]" -r requirements.txt From fc08be06bfefe11acc6ebf3d7ec84203c8038e69 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 12:32:40 -0700 Subject: [PATCH 4/8] fix: address second round of Copilot review feedback - Fix /etc/group entry to include GID field (name:x:gid:members) - Add set -euo pipefail and explicit writable checks for /etc/subuid and /etc/subgid before writing - Rename END_ID to SUB_ID_COUNT to clarify it is a count, not an end Made-with: Cursor --- devspaces/context/entrypoint.sh | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/devspaces/context/entrypoint.sh b/devspaces/context/entrypoint.sh index cb7284a5..a7f7c727 100644 --- a/devspaces/context/entrypoint.sh +++ b/devspaces/context/entrypoint.sh @@ -3,6 +3,7 @@ # Sets up the dynamic UID mapping required for rootless podman # with user namespaces (container-in-container without kubedock). # cspell: ignore subuid subgid catatonit +set -euo pipefail if [ ! -d "${HOME}" ]; then mkdir -p "${HOME}" @@ -11,7 +12,7 @@ fi if ! whoami &>/dev/null; then if [ -w /etc/passwd ]; then echo "${USER_NAME:-user}:x:$(id -u):0:${USER_NAME:-user} user:${HOME}:/bin/bash" >>/etc/passwd - echo "${USER_NAME:-user}:x:$(id -u):" >>/etc/group + echo "${USER_NAME:-user}:x:$(id -u):0:" >>/etc/group else echo "ERROR: Cannot resolve user and /etc/passwd is not writable" >&2 exit 1 @@ -30,13 +31,20 @@ else NAMESPACE_SIZE=65536 fi -END_ID=$(( NAMESPACE_SIZE - START_ID )) -if [ "${END_ID}" -le 0 ]; then +SUB_ID_COUNT=$(( NAMESPACE_SIZE - START_ID )) +if [ "${SUB_ID_COUNT}" -le 0 ]; then echo "ERROR: No subordinate IDs available (uid=${CURRENT_UID}, namespace=${NAMESPACE_SIZE})" >&2 exit 1 fi -echo "${USER}:${START_ID}:${END_ID}" >/etc/subuid -echo "${USER}:${START_ID}:${END_ID}" >/etc/subgid +for f in /etc/subuid /etc/subgid; do + if [ ! -w "$f" ]; then + echo "ERROR: ${f} is not writable, cannot configure rootless podman" >&2 + exit 1 + fi +done + +echo "${USER}:${START_ID}:${SUB_ID_COUNT}" >/etc/subuid +echo "${USER}:${START_ID}:${SUB_ID_COUNT}" >/etc/subgid exec /usr/libexec/podman/catatonit -- "$@" From 9208becddc5a17d07cdca57e5a0d218477e2aafa Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 13:08:52 -0700 Subject: [PATCH 5/8] chore: add BUILDAH to cspell dictionary Made-with: Cursor --- .config/dictionary.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/dictionary.txt b/.config/dictionary.txt index 44b423d5..da55fa6d 100644 --- a/.config/dictionary.txt +++ b/.config/dictionary.txt @@ -1,4 +1,5 @@ Ansibuddy +BUILDAH Containerfile Devfile PKGMGR From 90bb468d8750515831454481f66038b05c723eaa Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 13:12:39 -0700 Subject: [PATCH 6/8] fix: address human review - Dev Spaces 3.25+/OCP 4.20+ - Remove pod-overrides and container-overrides from devfile.yaml; Dev Spaces operator injects the correct context on 3.25+/4.20+ - Bump oc client to 4.20 (container-in-container supported in 4.20) Made-with: Cursor --- devfile.yaml | 10 ---------- tools/setup-image.sh | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/devfile.yaml b/devfile.yaml index 1725d84c..7a7461d1 100644 --- a/devfile.yaml +++ b/devfile.yaml @@ -3,16 +3,6 @@ metadata: name: ansible-demo components: - name: tooling-container - attributes: - pod-overrides: - metadata: - annotations: - io.kubernetes.cri-o.Devices: "/dev/fuse,/dev/net/tun" - spec: - hostUsers: false - container-overrides: - securityContext: - procMount: Unmasked container: image: ghcr.io/ansible/ansible-devspaces:latest memoryRequest: 256M diff --git a/tools/setup-image.sh b/tools/setup-image.sh index 1931797e..3cc2e09e 100755 --- a/tools/setup-image.sh +++ b/tools/setup-image.sh @@ -5,7 +5,7 @@ set -exuo pipefail # Install oc client -OC_VERSION=4.17 +OC_VERSION=4.20 curl -s -L "https://mirror.openshift.com/pub/openshift-v4/$(arch)/clients/ocp/stable-${OC_VERSION}/openshift-client-linux.tar.gz" | tar -C /usr/local/bin -xz --no-same-owner chmod +x /usr/local/bin/oc oc version --client=true From cc0203a646f64e9a391ab4dcf6733e93647dbc85 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 13:17:39 -0700 Subject: [PATCH 7/8] fix: remove /etc/group append from entrypoint Only /etc/passwd is needed for whoami; user primary GID is 0 and group 0 (root) already exists in the image. Avoids invalid group-file format and GID mismatch (Copilot 2949320604). Made-with: Cursor --- devspaces/context/entrypoint.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/devspaces/context/entrypoint.sh b/devspaces/context/entrypoint.sh index a7f7c727..7e9f88ed 100644 --- a/devspaces/context/entrypoint.sh +++ b/devspaces/context/entrypoint.sh @@ -12,7 +12,6 @@ fi if ! whoami &>/dev/null; then if [ -w /etc/passwd ]; then echo "${USER_NAME:-user}:x:$(id -u):0:${USER_NAME:-user} user:${HOME}:/bin/bash" >>/etc/passwd - echo "${USER_NAME:-user}:x:$(id -u):0:" >>/etc/group else echo "ERROR: Cannot resolve user and /etc/passwd is not writable" >&2 exit 1 From 13724c9e38ef2f8c254ac0c07d7ce05b53b6da93 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 17 Mar 2026 13:24:27 -0700 Subject: [PATCH 8/8] =?UTF-8?q?docs(entrypoint):=20clarify=20comment=20?= =?UTF-8?q?=E2=80=94=20subordinate=20count=20from=20UID=20map,=20used=20fo?= =?UTF-8?q?r=20both=20subuid/subgid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- devspaces/context/entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devspaces/context/entrypoint.sh b/devspaces/context/entrypoint.sh index 7e9f88ed..9f106053 100644 --- a/devspaces/context/entrypoint.sh +++ b/devspaces/context/entrypoint.sh @@ -22,8 +22,8 @@ USER=$(whoami) CURRENT_UID=$(id -u) START_ID=$(( CURRENT_UID + 1 )) -# Derive the available UID/GID range from the namespace mapping -# rather than assuming a fixed 65536 window. +# Derive the available subordinate ID count from the UID namespace mapping +# (same count used for both subuid and subgid). if [ -r /proc/self/uid_map ]; then NAMESPACE_SIZE=$(awk '{print $3}' /proc/self/uid_map | head -n1) else