From 9f0bad8c0d0a2e5f6fa8c28dc6417adaac63294b Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:51:12 -0700 Subject: [PATCH 01/15] build: Add apt_sources build secret for Artifactory package mirror Add an optional 'apt_sources' build secret that overlays /etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list for the duration of each apt step, so package installs in Dockerfile and Dockerfile.buildbase resolve through the NVIDIA Artifactory mirror. The source list carries credentials, so it is passed via 'docker build --secret' and mounted per RUN instruction rather than copied in. It never becomes part of an image layer. A secret mount is scoped to a single RUN, so every apt block gets its own mount. The first block in the build base is deliberately left alone: it bootstraps ca-certificates from the distribution repositories, and a base image without CA certificates (ubuntu:24.04 ships none) cannot complete a TLS handshake with Artifactory until that install finishes. When the secret is absent the generated Dockerfiles are byte-identical to before and apt uses the distribution repositories, so the default build path is unchanged. Also gate the existing vllm secrets on the 'req' key instead of on a non-empty secrets dict. Passing only 'apt_sources' previously made the dict truthy and emitted '--secret id=req,src=' with an empty source, failing the build. (cherry picked from commit ac60633a214960148aaeef8948f7186ff2f9f198) --- build.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index 68ee00b91f..05116f3e79 100755 --- a/build.py +++ b/build.py @@ -878,6 +878,50 @@ def install_dcgm_libraries(dcgm_version, target_machine): ) +def apt_sources_secret_mount(): + """Return the RUN mount flag that overlays the NVIDIA Artifactory apt + source list, or an empty string when the secret was not requested. + + The secret is supplied as '--build-secret apt_sources '. When it is + absent this returns "" and the generated Dockerfiles are byte-identical to + a build without this feature, so the default path is unchanged. + """ + secrets = dict(getattr(FLAGS, "build_secret", None) or []) + if not secrets.get("apt_sources"): + return "" + return ( + "--mount=type=secret,id=apt_sources," + "target=/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list," + "required=false " + ) + + +def mount_apt_sources_secret(df, skip=0): + """Prefix each 'RUN apt-get update' in df with the Artifactory source-list + secret mount, leaving the first 'skip' occurrences untouched. + + A secret mount is scoped to a single RUN instruction, so every apt block + that should resolve through Artifactory needs its own mount. The build base + skips its first block: that block bootstraps ca-certificates from the + distribution repositories, and a base image without CA certificates + (ubuntu:24.04 ships none) cannot complete a TLS handshake with Artifactory + until it has finished. + """ + mount = apt_sources_secret_mount() + if not mount: + return df + + marker = "RUN apt-get update" + chunks = df.split(marker) + result = chunks[0] + for i, chunk in enumerate(chunks[1:]): + if i < skip: + result += marker + chunk + else: + result += "RUN " + mount + "apt-get update" + chunk + return result + + def create_dockerfile_buildbase_rhel(ddir, dockerfile_name, argmap): df = """ ARG TRITON_VERSION={} @@ -1131,6 +1175,8 @@ def create_dockerfile_buildbase(ddir, dockerfile_name, argmap): ENTRYPOINT [] """ + df = mount_apt_sources_secret(df, skip=1) + with open(os.path.join(ddir, dockerfile_name), "w") as dfile: dfile.write(df) @@ -1259,6 +1305,8 @@ def create_dockerfile_linux( ldconfig """ + df = mount_apt_sources_secret(df) + with open(os.path.join(ddir, dockerfile_name), "w") as dfile: dfile.write(df) @@ -1683,6 +1731,11 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ baseargs += ["--cache-from={}".format(k) for k in cachefrommap] + if secrets.get("apt_sources"): + baseargs += [ + "--secret id=apt_sources,src={}".format(secrets["apt_sources"]), + ] + baseargs += ["."] docker_script.cwd(THIS_SCRIPT_DIR) @@ -1785,7 +1838,7 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ "docker", "build", ] - if secrets: + if secrets.get("req"): finalargs += [ f"--secret id=req,src={requirements}", "--secret id=VLLM_INDEX_URL", @@ -1793,6 +1846,10 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ "--secret id=NVPL_SLIM_URL", f"--build-arg BUILD_PUBLIC_VLLM={build_public_vllm}", ] + if secrets.get("apt_sources"): + finalargs += [ + "--secret id=apt_sources,src={}".format(secrets["apt_sources"]), + ] finalargs += [ "-t", "tritonserver", @@ -2630,7 +2687,11 @@ def enable_all(): metavar=("key", "value"), help="Add build secrets in the form of . These secrets are used during the build process for vllm. The secrets are passed to the Docker build step as `--secret id=`. The following keys are expected and their purposes are described below:\n\n" " - 'req': A file containing a list of dependencies for pip (e.g., requirements.txt).\n" - " - 'build_public_vllm': A flag (default is 'true') indicating whether to build the public VLLM version.\n\n" + " - 'build_public_vllm': A flag (default is 'true') indicating whether to build the public VLLM version.\n" + " - 'apt_sources': A file mounted at /etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list for the\n" + " duration of each apt step, so package installs resolve through the NVIDIA Artifactory mirror. It\n" + " holds credentials, so it is passed as a secret and never written to an image layer. When omitted\n" + " the generated Dockerfiles are unchanged and apt uses the distribution repositories.\n\n" "Ensure that the required environment variables for these secrets are set before running the build.", ) parser.add_argument( From 626539516b802bec912efeea676b1244c42afe74 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:57:49 -0700 Subject: [PATCH 02/15] build: Accept Docker --secret syntax in build.py Add a --secret flag that takes a Docker build secret spec and forwards it to 'docker build --secret' unchanged, so every form Docker accepts works here: 'id=', 'id=,src=' and 'id=,env='. The flag is repeatable and the spec is never rewritten. The id is parsed by scanning the comma separated fields rather than by position, because Docker does not require 'id' to come first. A secret whose id is 'apt_sources' still drives the apt source list mount in the generated Dockerfile and Dockerfile.buildbase, whichever flag declared it, so --secret id=apt_sources,src= and --build-secret apt_sources produce identical output. --build-secret keeps working unchanged, including the vllm keys, which are not re-emitted through the new path. (cherry picked from commit 6b733c087c03094a384941ee59e757741721dd0c) --- build.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/build.py b/build.py index 05116f3e79..701ab6ce36 100755 --- a/build.py +++ b/build.py @@ -878,16 +878,49 @@ def install_dcgm_libraries(dcgm_version, target_machine): ) +def secret_spec_id(spec): + """Return the 'id' of a Docker --secret spec, or "" when it has none. + + Docker does not require 'id' to come first, so the fields are scanned + rather than indexed. + """ + for field in spec.split(","): + key, _, value = field.partition("=") + if key.strip() == "id": + return value.strip() + return "" + + +def secret_build_args(): + """Return the '--secret' arguments to forward to 'docker build'. + + Specs given with --secret are passed through untouched. Pairs given with + the older --build-secret are rendered into the equivalent Docker spec. + """ + args = ["--secret {}".format(spec) for spec in getattr(FLAGS, "secret", None) or []] + for key, value in getattr(FLAGS, "build_secret", None) or []: + if key == "apt_sources" and value: + args.append("--secret id={},src={}".format(key, value)) + return args + + +def declared_secret_ids(): + """Ids of every secret supplied with --secret or --build-secret.""" + ids = {secret_spec_id(spec) for spec in getattr(FLAGS, "secret", None) or []} + ids |= {key for key, value in getattr(FLAGS, "build_secret", None) or [] if value} + return {i for i in ids if i} + + def apt_sources_secret_mount(): """Return the RUN mount flag that overlays the NVIDIA Artifactory apt source list, or an empty string when the secret was not requested. - The secret is supplied as '--build-secret apt_sources '. When it is - absent this returns "" and the generated Dockerfiles are byte-identical to - a build without this feature, so the default path is unchanged. + The secret is supplied either as '--secret id=apt_sources,src=' or + as '--build-secret apt_sources '. When it is absent this returns "" + and the generated Dockerfiles are byte-identical to a build without this + feature, so the default path is unchanged. """ - secrets = dict(getattr(FLAGS, "build_secret", None) or []) - if not secrets.get("apt_sources"): + if "apt_sources" not in declared_secret_ids(): return "" return ( "--mount=type=secret,id=apt_sources," @@ -1731,10 +1764,7 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ baseargs += ["--cache-from={}".format(k) for k in cachefrommap] - if secrets.get("apt_sources"): - baseargs += [ - "--secret id=apt_sources,src={}".format(secrets["apt_sources"]), - ] + baseargs += secret_build_args() baseargs += ["."] @@ -1846,10 +1876,7 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ "--secret id=NVPL_SLIM_URL", f"--build-arg BUILD_PUBLIC_VLLM={build_public_vllm}", ] - if secrets.get("apt_sources"): - finalargs += [ - "--secret id=apt_sources,src={}".format(secrets["apt_sources"]), - ] + finalargs += secret_build_args() finalargs += [ "-t", "tritonserver", @@ -2679,6 +2706,24 @@ def enable_all(): default=DEFAULT_TRITON_VERSION_MAP["rhel_py_version"], help="This flag sets the Python version for RHEL platform of Triton Inference Server to be built. Default: the latest supported version.", ) + parser.add_argument( + "--secret", + action="append", + required=False, + metavar="spec", + help="Pass a build secret to 'docker build' using Docker's own syntax. The spec is forwarded " + "unchanged, so every form 'docker build --secret' accepts is supported:\n\n" + " - 'id=,src=' read the secret from a file (source= is an accepted alias)\n" + " - 'id=,env=' read the secret from an environment variable\n" + " - 'id=' read the environment variable of the same name\n\n" + "May be repeated. The secret is available to a Dockerfile step that mounts it with " + "'RUN --mount=type=secret,id=', and never becomes part of an image layer.\n\n" + "A secret with id 'apt_sources' is also mounted automatically over " + "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list for the duration of each apt step in the " + "generated Dockerfile and Dockerfile.buildbase, so package installs resolve through the NVIDIA " + "Artifactory mirror. When it is omitted the generated Dockerfiles are unchanged and apt uses the " + "distribution repositories.", + ) parser.add_argument( "--build-secret", action="append", @@ -2729,6 +2774,8 @@ def enable_all(): FLAGS.extra_backend_cmake_arg = [] if FLAGS.build_secret is None: FLAGS.build_secret = [] + if FLAGS.secret is None: + FLAGS.secret = [] FLAGS.boost_url = os.getenv( "TRITON_BOOST_URL", From cd39e03ab1d9980a1967b67354a801d16f168f3f Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:03:24 -0700 Subject: [PATCH 03/15] build: Merge build secret flags into --docker-build-secret Replace --secret and --build-secret with a single --docker-build-secret that takes a Docker build secret spec and forwards it to 'docker build --secret' unchanged, so every form Docker accepts works: 'id=', 'id=,src=' and 'id=,env='. The flag is repeatable and order is preserved. The id is parsed by scanning the comma separated fields rather than by position, because Docker does not require 'id' to come first. The id 'apt_sources' keeps its extra meaning and still drives the apt source list mount in the generated Dockerfile and Dockerfile.buildbase. Drop the vllm secret handling. The VLLM_INDEX_URL, PYTORCH_TRITON_URL and NVPL_SLIM_URL secrets were forwarded to 'docker build' but no generated Dockerfile ever mounted them, and BUILD_PUBLIC_VLLM was a build argument carried on a flag named for secrets. A requirements file can still be passed as an ordinary secret with --docker-build-secret id=req,src=. This is a breaking change to the command line: --secret and --build-secret no longer exist. The only caller in the tree is the tritonserver CI template, updated alongside this commit. (cherry picked from commit 87b8f24767b68092f61213689284dc610ad08c68) --- build.py | 66 +++++++++++++++----------------------------------------- 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/build.py b/build.py index 701ab6ce36..0c22d287da 100755 --- a/build.py +++ b/build.py @@ -891,24 +891,23 @@ def secret_spec_id(spec): return "" +def declared_secret_specs(): + """Every --docker-build-secret spec, in the order it was given.""" + return [spec for spec in getattr(FLAGS, "docker_build_secret", None) or [] if spec] + + +def declared_secret_ids(): + """Ids of every secret supplied with --docker-build-secret.""" + return {i for i in (secret_spec_id(s) for s in declared_secret_specs()) if i} + + def secret_build_args(): """Return the '--secret' arguments to forward to 'docker build'. - Specs given with --secret are passed through untouched. Pairs given with - the older --build-secret are rendered into the equivalent Docker spec. + Specs are passed through untouched, so anything 'docker build --secret' + accepts works here. """ - args = ["--secret {}".format(spec) for spec in getattr(FLAGS, "secret", None) or []] - for key, value in getattr(FLAGS, "build_secret", None) or []: - if key == "apt_sources" and value: - args.append("--secret id={},src={}".format(key, value)) - return args - - -def declared_secret_ids(): - """Ids of every secret supplied with --secret or --build-secret.""" - ids = {secret_spec_id(spec) for spec in getattr(FLAGS, "secret", None) or []} - ids |= {key for key, value in getattr(FLAGS, "build_secret", None) or [] if value} - return {i for i in ids if i} + return ["--secret {}".format(spec) for spec in declared_secret_specs()] def apt_sources_secret_mount(): @@ -1868,14 +1867,6 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ "docker", "build", ] - if secrets.get("req"): - finalargs += [ - f"--secret id=req,src={requirements}", - "--secret id=VLLM_INDEX_URL", - "--secret id=PYTORCH_TRITON_URL", - "--secret id=NVPL_SLIM_URL", - f"--build-arg BUILD_PUBLIC_VLLM={build_public_vllm}", - ] finalargs += secret_build_args() finalargs += [ "-t", @@ -2707,7 +2698,7 @@ def enable_all(): help="This flag sets the Python version for RHEL platform of Triton Inference Server to be built. Default: the latest supported version.", ) parser.add_argument( - "--secret", + "--docker-build-secret", action="append", required=False, metavar="spec", @@ -2718,27 +2709,12 @@ def enable_all(): " - 'id=' read the environment variable of the same name\n\n" "May be repeated. The secret is available to a Dockerfile step that mounts it with " "'RUN --mount=type=secret,id=', and never becomes part of an image layer.\n\n" - "A secret with id 'apt_sources' is also mounted automatically over " + "The id 'apt_sources' carries extra meaning: it is also mounted over " "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list for the duration of each apt step in the " "generated Dockerfile and Dockerfile.buildbase, so package installs resolve through the NVIDIA " "Artifactory mirror. When it is omitted the generated Dockerfiles are unchanged and apt uses the " "distribution repositories.", ) - parser.add_argument( - "--build-secret", - action="append", - required=False, - nargs=2, - metavar=("key", "value"), - help="Add build secrets in the form of . These secrets are used during the build process for vllm. The secrets are passed to the Docker build step as `--secret id=`. The following keys are expected and their purposes are described below:\n\n" - " - 'req': A file containing a list of dependencies for pip (e.g., requirements.txt).\n" - " - 'build_public_vllm': A flag (default is 'true') indicating whether to build the public VLLM version.\n" - " - 'apt_sources': A file mounted at /etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list for the\n" - " duration of each apt step, so package installs resolve through the NVIDIA Artifactory mirror. It\n" - " holds credentials, so it is passed as a secret and never written to an image layer. When omitted\n" - " the generated Dockerfiles are unchanged and apt uses the distribution repositories.\n\n" - "Ensure that the required environment variables for these secrets are set before running the build.", - ) parser.add_argument( "--triton-wheels-dependencies-group", required=False, @@ -2772,10 +2748,8 @@ def enable_all(): FLAGS.override_backend_cmake_arg = [] if FLAGS.extra_backend_cmake_arg is None: FLAGS.extra_backend_cmake_arg = [] - if FLAGS.build_secret is None: - FLAGS.build_secret = [] - if FLAGS.secret is None: - FLAGS.secret = [] + if FLAGS.docker_build_secret is None: + FLAGS.docker_build_secret = [] FLAGS.boost_url = os.getenv( "TRITON_BOOST_URL", @@ -2885,12 +2859,6 @@ def enable_all(): ) backends["python"] = backends["vllm"] - secrets = dict(getattr(FLAGS, "build_secret", [])) - if secrets: - requirements = secrets.get("req", "") - build_public_vllm = secrets.get("build_public_vllm", "true") - log('Build Arg for BUILD_PUBLIC_VLLM: "{}"'.format(build_public_vllm)) - # Initialize map of repo agents to build and repo-tag for each. repoagents = {} for be in FLAGS.repoagent: From 2587ac262f83dc3e229d1484333f450cd8883b09 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:16:34 -0700 Subject: [PATCH 04/15] build: Support target= in --docker-build-secret specs Docker rejects 'target' on 'docker build --secret' because the key belongs to the Dockerfile mount, not the build command. build.py generates the Dockerfiles and runs the build, so it can accept the key on either side: split each spec, forward only what the build command understands, and apply the rest where the Dockerfile is generated. An apt_sources secret now mounts on its 'target' when the spec sets one, so the source list no longer has to land on the NVIDIA Artifactory path. Without a 'target' it keeps that path, so existing invocations are unaffected. Spec parsing moves to a shared helper, since 'id' and 'target' are both looked up by name rather than by position. (cherry picked from commit d9000d86bba5d8ebcc8d77b2c62a733ba86cffcf) --- build.py | 97 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/build.py b/build.py index 0c22d287da..6deac7e304 100755 --- a/build.py +++ b/build.py @@ -878,19 +878,45 @@ def install_dcgm_libraries(dcgm_version, target_machine): ) -def secret_spec_id(spec): - """Return the 'id' of a Docker --secret spec, or "" when it has none. +# Keys that 'docker build --secret' accepts. A spec may also carry keys that +# only the Dockerfile side understands ('target', 'required', 'mode', ...); +# passing those to the build command is an error, so they are filtered out. +DOCKER_BUILD_SECRET_KEYS = ("id", "src", "source", "env", "type") + +# Id whose secret is mounted over the apt source list, and where it lands when +# the spec does not say. +APT_SOURCES_SECRET_ID = "apt_sources" +APT_SOURCES_DEFAULT_TARGET = "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list" + - Docker does not require 'id' to come first, so the fields are scanned - rather than indexed. +def parse_secret_spec(spec): + """Split a Docker secret spec into ordered (key, value) pairs. + + Docker does not require any particular field order, so callers look up + keys by name rather than by position. """ + fields = [] for field in spec.split(","): key, _, value = field.partition("=") - if key.strip() == "id": - return value.strip() + key = key.strip() + if key: + fields.append((key, value.strip())) + return fields + + +def secret_spec_value(spec, name): + """Return the value of one field of a secret spec, or "" when unset.""" + for key, value in parse_secret_spec(spec): + if key == name: + return value return "" +def secret_spec_id(spec): + """Return the 'id' of a Docker secret spec, or "" when it has none.""" + return secret_spec_value(spec, "id") + + def declared_secret_specs(): """Every --docker-build-secret spec, in the order it was given.""" return [spec for spec in getattr(FLAGS, "docker_build_secret", None) or [] if spec] @@ -904,28 +930,40 @@ def declared_secret_ids(): def secret_build_args(): """Return the '--secret' arguments to forward to 'docker build'. - Specs are passed through untouched, so anything 'docker build --secret' - accepts works here. + Only the keys the build command understands are forwarded. Keys that + belong to the Dockerfile mount are dropped here and applied where the + Dockerfile is generated instead. """ - return ["--secret {}".format(spec) for spec in declared_secret_specs()] + args = [] + for spec in declared_secret_specs(): + fields = [ + "{}={}".format(key, value) + for key, value in parse_secret_spec(spec) + if key in DOCKER_BUILD_SECRET_KEYS + ] + if fields: + args.append("--secret {}".format(",".join(fields))) + return args def apt_sources_secret_mount(): - """Return the RUN mount flag that overlays the NVIDIA Artifactory apt - source list, or an empty string when the secret was not requested. - - The secret is supplied either as '--secret id=apt_sources,src=' or - as '--build-secret apt_sources '. When it is absent this returns "" - and the generated Dockerfiles are byte-identical to a build without this - feature, so the default path is unchanged. + """Return the RUN mount flag that overlays the apt source list, or an + empty string when no such secret was requested. + + The mount point comes from the spec's 'target' when it has one, so + '--docker-build-secret id=apt_sources,src=,target=' controls + where the list lands. Without a 'target' it defaults to the NVIDIA + Artifactory source list. When the secret is absent this returns "" and the + generated Dockerfiles are byte-identical to a build without this feature. """ - if "apt_sources" not in declared_secret_ids(): - return "" - return ( - "--mount=type=secret,id=apt_sources," - "target=/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list," - "required=false " - ) + for spec in declared_secret_specs(): + if secret_spec_id(spec) != APT_SOURCES_SECRET_ID: + continue + target = secret_spec_value(spec, "target") or APT_SOURCES_DEFAULT_TARGET + return "--mount=type=secret,id={},target={},required=false ".format( + APT_SOURCES_SECRET_ID, target + ) + return "" def mount_apt_sources_secret(df, skip=0): @@ -2709,11 +2747,14 @@ def enable_all(): " - 'id=' read the environment variable of the same name\n\n" "May be repeated. The secret is available to a Dockerfile step that mounts it with " "'RUN --mount=type=secret,id=', and never becomes part of an image layer.\n\n" - "The id 'apt_sources' carries extra meaning: it is also mounted over " - "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list for the duration of each apt step in the " - "generated Dockerfile and Dockerfile.buildbase, so package installs resolve through the NVIDIA " - "Artifactory mirror. When it is omitted the generated Dockerfiles are unchanged and apt uses the " - "distribution repositories.", + "A spec may additionally carry 'target='. Docker rejects that key on the command line " + "because it belongs to the Dockerfile mount, so it is stripped from the build command and " + "applied where the Dockerfile is generated.\n\n" + "The id 'apt_sources' carries extra meaning: it is also mounted for the duration of each apt step " + "in the generated Dockerfile and Dockerfile.buildbase, so package installs resolve through a " + "package mirror. It lands on 'target' when the spec sets one, and on " + "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list otherwise. When the secret is omitted the " + "generated Dockerfiles are unchanged and apt uses the distribution repositories.", ) parser.add_argument( "--triton-wheels-dependencies-group", From b0ee78a4382bbe489f2960b6e5ecf8e8b686bb59 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:29:29 -0700 Subject: [PATCH 05/15] build: Authenticate GitHub clones made by cmake_build cmake_build clones the component and backend repositories from GitHub while running inside the build base container, not while any image is being built, so a docker build secret cannot reach it. Configure a git credential helper in the build base instead and supply the token when the container starts. The helper stores no credential, only a reference to GITHUB_TOKEN, so it is safe both in an image layer and in the build base image that CI pushes to the registry. It also keeps the token out of .git/config, which an authenticated clone URL would not. The token is forwarded as 'docker run -e GITHUB_TOKEN', by name rather than as NAME=value, so the value is taken from the ambient environment and never written into the generated docker_build script, which CI publishes as a build artifact. The helper exits without printing anything when GITHUB_TOKEN is empty, so a build without a token keeps cloning anonymously rather than offering an empty password and failing a clone that previously worked. (cherry picked from commit 72b657000655aca26c7f286348214ecf1ee87a0f) --- build.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/build.py b/build.py index 6deac7e304..0759ff5462 100755 --- a/build.py +++ b/build.py @@ -1236,6 +1236,23 @@ def create_dockerfile_buildbase(ddir, dockerfile_name, argmap): os.getenv("CCACHE_REMOTE_STORAGE") ) + # Authenticate GitHub clones. cmake_build clones the component and backend + # repositories while running inside this container, not while the image is + # being built, so a docker build secret cannot reach it. Configure a + # credential helper instead: it holds no credential itself, only a + # reference to GITHUB_TOKEN, so it is safe in a layer and in the pushed + # build base image. The value is supplied by 'docker run -e GITHUB_TOKEN'. + # + # The helper exits without printing anything when GITHUB_TOKEN is empty, + # which leaves git to fall back to unauthenticated access rather than + # offering an empty password and failing a clone that used to work. + df += """ +RUN git config --global credential."https://github.com".helper \\ + '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \\ + echo username=x-access-token; \\ + echo "password=${GITHUB_TOKEN}"; }; f' +""" + # Copy in the triton source. We remove existing contents first in # case the FROM container has something there already. df += """ @@ -1849,6 +1866,13 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ # or explicit --release-version). Dev / pre-release builds leave it # unset so build_wheel.py reads the in-tree TRITON_VERSION file and # takes the PEP 817 variant path. + # Forward the GitHub token to the clones cmake_build performs in this + # container. Passed by name rather than as NAME=value: 'docker run -e + # VAR' takes the value from the ambient environment, so the token stays + # out of this script, which CI publishes as a build artifact. + if os.environ.get("GITHUB_TOKEN"): + runargs += ["-e", "GITHUB_TOKEN"] + if "TRITON_RELEASE_VERSION" in os.environ: runargs += [ "-e", From f189c1a41b81d0e45f3d4f440f1ea185ff21af38 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:34:45 -0700 Subject: [PATCH 06/15] build: Authenticate GitHub clones in the SDK and QA images cmake clones the component repositories from GitHub while these images are built, so unlike the build base these steps are reachable by a docker build secret. Configure a git credential helper and mount the token on the steps that clone. The helper stores no credential, only a reference to GITHUB_TOKEN, so it is safe in a layer and in the pushed image. It also keeps the token out of .git/config, which an authenticated clone URL would not. Because a credential helper does not cross a build stage, it is configured once per stage that clones: the client build and the Model Analyzer install in Dockerfile.sdk, and the CI base stage in Dockerfile.QA. Every mount is 'required=false' and the helper prints nothing when the token is absent, so a build without the secret clones anonymously exactly as before. (cherry picked from commit 77627e5cd96fca250bd907264f0362166b6a279c) --- Dockerfile.QA | 16 ++++++++++++++-- Dockerfile.sdk | 24 ++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Dockerfile.QA b/Dockerfile.QA index 6560b2c5e6..8d9453a1d8 100644 --- a/Dockerfile.QA +++ b/Dockerfile.QA @@ -175,9 +175,20 @@ RUN mkdir -p qa/custom_models/custom_sequence_int32/1 && \ cp tritonbuild/tritonserver/backends/dyna_sequence/libtriton_dyna_sequence.so \ qa/custom_models/custom_dyna_sequence_int32/1/. +# Authenticate the GitHub clones cmake performs for the component repos. The +# helper stores no credential, only a reference to GITHUB_TOKEN, so it is safe +# in a layer and in the pushed image. Each step that clones supplies the value +# with --mount=type=secret,id=github_token. The helper prints nothing when the +# token is absent, leaving git to clone anonymously as before. +RUN git config --global credential."https://github.com".helper \ + '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \ + echo username=x-access-token; \ + echo "password=${GITHUB_TOKEN}"; }; f' + # L0_lifecycle needs No-GPU build of identity backend. WORKDIR /workspace/tritonbuild/identity -RUN rm -rf install build && mkdir build && cd build && \ +RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ + rm -rf install build && mkdir build && cd build && \ cmake -DTRITON_ENABLE_GPU=OFF \ -DCMAKE_INSTALL_PREFIX:PATH=/workspace/tritonbuild/identity/install \ -DTRITON_REPO_ORGANIZATION:STRING=${TRITON_REPO_ORGANIZATION} \ @@ -190,7 +201,8 @@ RUN rm -rf install build && mkdir build && cd build && \ # L0_backend_python test require triton_shm_monitor ARG TRITON_BOOST_URL="https://archives.boost.io/release/1.80.0/source/boost_1_80_0.tar.gz" WORKDIR /workspace/tritonbuild/python -RUN rm -rf install build && mkdir build && cd build && \ +RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ + rm -rf install build && mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX:PATH=/workspace/tritonbuild/python/install \ -DTRITON_REPO_ORGANIZATION:STRING=${TRITON_REPO_ORGANIZATION} \ -DTRITON_COMMON_REPO_TAG:STRING=${TRITON_COMMON_REPO_TAG} \ diff --git a/Dockerfile.sdk b/Dockerfile.sdk index 6697aff946..e88893dfe8 100644 --- a/Dockerfile.sdk +++ b/Dockerfile.sdk @@ -124,8 +124,19 @@ WORKDIR /workspace COPY TRITON_VERSION . COPY ${TRITON_CLIENT_REPO_SUBDIR} client +# Authenticate the GitHub clones cmake performs for the component repos. The +# helper stores no credential, only a reference to GITHUB_TOKEN, so it is safe +# in a layer and in the pushed image. Each step that clones supplies the value +# with --mount=type=secret,id=github_token. The helper prints nothing when the +# token is absent, leaving git to clone anonymously as before. +RUN git config --global credential."https://github.com".helper \ + '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \ + echo username=x-access-token; \ + echo "password=${GITHUB_TOKEN}"; }; f' + WORKDIR /workspace/client_build -RUN cmake -DCMAKE_INSTALL_PREFIX=/workspace/install \ +RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ + cmake -DCMAKE_INSTALL_PREFIX=/workspace/install \ -DTRITON_VERSION=`cat /workspace/TRITON_VERSION` \ -DTRITON_REPO_ORGANIZATION=${TRITON_REPO_ORGANIZATION} \ -DTRITON_COMMON_REPO_TAG=${TRITON_COMMON_REPO_TAG} \ @@ -138,11 +149,13 @@ RUN cmake -DCMAKE_INSTALL_PREFIX=/workspace/install \ -DTRITON_ENABLE_EXAMPLES=ON -DTRITON_ENABLE_TESTS=ON \ -DTRITON_ENABLE_GPU=${TRITON_ENABLE_GPU} /workspace/client RUN --mount=type=secret,id=maven_settings,target=/run/secrets/maven_settings,required=false \ + --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ if [ -f /run/secrets/maven_settings ]; then export MAVEN_ARGS="--settings /run/secrets/maven_settings"; fi && \ cmake --build . -v --parallel ${TRITON_CLIENT_BUILD_PARALLEL} --target cc-clients java-clients python-clients # Install Java API Bindings RUN --mount=type=secret,id=maven_settings,target=/run/secrets/maven_settings,required=false \ + --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ if [ -f /run/secrets/maven_settings ]; then export MAVEN_ARGS="--settings /run/secrets/maven_settings"; fi && \ if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ source /workspace/client/src/java-api-bindings/scripts/install_dependencies_and_build.sh \ @@ -257,7 +270,14 @@ RUN rm -f /usr/bin/python && \ # Install Model Analyzer ARG TRITON_MODEL_ANALYZER_REPO_TAG ARG TRITON_MODEL_ANALYZER_REPO="${TRITON_REPO_ORGANIZATION}/model_analyzer@${TRITON_MODEL_ANALYZER_REPO_TAG}" -RUN pip3 install "git+${TRITON_MODEL_ANALYZER_REPO}" +# Separate stage from the client build, so the credential helper is configured +# again here. It stores no credential, only a reference to GITHUB_TOKEN. +RUN git config --global credential."https://github.com".helper \ + '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \ + echo username=x-access-token; \ + echo "password=${GITHUB_TOKEN}"; }; f' +RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ + pip3 install "git+${TRITON_MODEL_ANALYZER_REPO}" # Entrypoint Banner ENV NVIDIA_PRODUCT_NAME="Triton Server SDK" From e02d4125e9df65e4ce15bb6df068f8483627ceef Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:39:01 -0700 Subject: [PATCH 07/15] build: Route SDK and QA package installs through the apt source list secret Mount the apt source list on the package installation steps of Dockerfile.sdk and Dockerfile.QA, so they resolve through the configured mirror the same way the generated Dockerfile and Dockerfile.buildbase already do. The list carries credentials, so it is mounted per step rather than copied in, and never becomes part of an image layer. A secret mount lasts for one RUN, so each apt step carries its own. The first step of the SDK client build is deliberately left alone: it installs ca-certificates, and a base image without them cannot complete a TLS handshake with the mirror until that install finishes. Its packages continue to come from the distribution repositories. Every mount is 'required=false', so a build without the secret installs from the distribution repositories exactly as before. (cherry picked from commit 9fd537995e76abd61344418aeca181abde5c78cb) --- Dockerfile.QA | 9 ++++++--- Dockerfile.sdk | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Dockerfile.QA b/Dockerfile.QA index 8d9453a1d8..2754180ea4 100644 --- a/Dockerfile.QA +++ b/Dockerfile.QA @@ -52,7 +52,8 @@ ARG IGPU_BUILD # Ensure apt-get won't prompt for selecting options ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && \ +RUN --mount=type=secret,id=apt_sources,target=/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list,required=false \ + apt-get update && \ apt-get install -y --no-install-recommends \ build-essential \ libarchive-dev \ @@ -328,7 +329,8 @@ ARG TARGETPLATFORM ENV DEBIAN_FRONTEND=noninteractive # install platform specific packages -RUN if grep -qE '^VERSION_ID="(18\.04|20\.04|22\.04|24\.04)' /etc/os-release; then \ +RUN --mount=type=secret,id=apt_sources,target=/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list,required=false \ + if grep -qE '^VERSION_ID="(18\.04|20\.04|22\.04|24\.04)' /etc/os-release; then \ apt-get update && \ apt-get install -y --no-install-recommends \ libpng-dev && \ @@ -341,7 +343,8 @@ RUN if grep -qE '^VERSION_ID="(18\.04|20\.04|22\.04|24\.04)' /etc/os-release; th # CI/QA for memcheck requires valgrind # libarchive-dev is required by Python backend -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN --mount=type=secret,id=apt_sources,target=/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list,required=false \ + apt-get update && apt-get install -y --no-install-recommends \ curl \ gdb \ libarchive-dev \ diff --git a/Dockerfile.sdk b/Dockerfile.sdk index e88893dfe8..3ae6263453 100644 --- a/Dockerfile.sdk +++ b/Dockerfile.sdk @@ -180,7 +180,8 @@ ARG TRITON_CORE_REPO_TAG ARG TARGETPLATFORM ARG TRITON_ENABLE_GPU -RUN apt-get update && \ +RUN --mount=type=secret,id=apt_sources,target=/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list,required=false \ + apt-get update && \ apt-get install -y --no-install-recommends \ curl \ default-jdk \ From 172ff1a884e251612908b02dbbf5423ffb9ddd53 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 22:13:33 -0700 Subject: [PATCH 08/15] docs: Document build secrets and GitHub clone authentication Describe --docker-build-secret in the Docker build section: the spec forms it accepts, that a secret never reaches an image layer, and that target= is applied when generating the Dockerfile because docker rejects it on the command line. Cover the apt_sources id, including why the first apt step of the build base is excluded from the mount, and GITHUB_TOKEN, including why it is an environment variable rather than a build secret. (cherry picked from commit e233b05abfa7982977665bfedeb25a7fa4ed7dfc) --- docs/customization_guide/build.md | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/customization_guide/build.md b/docs/customization_guide/build.md index b37f579339..e33693b392 100644 --- a/docs/customization_guide/build.md +++ b/docs/customization_guide/build.md @@ -180,6 +180,57 @@ you have a branch called "mybranch" in the repo that you want to use in the build, you would specify --backend=onnxruntime:mybranch. +#### Build Secrets + +Some builds need a credential, for example an apt source list naming an +authenticated package mirror. Pass these with --docker-build-secret, which +takes a Docker build secret spec and forwards it to `docker build --secret` +unchanged. Every form Docker accepts works and the flag may be repeated. + +```bash +$ ./build.py ... --docker-build-secret id=,src= --docker-build-secret id=,env= +``` + +A secret is mounted only for the duration of the build step that uses it and +never becomes part of an image layer. + +A spec may also carry target=``, which selects where the secret is +mounted. Docker rejects that key on the command line because it belongs to the +Dockerfile rather than the build command, so build.py removes it before +invoking docker and applies it when generating the Dockerfile. + +The id `apt_sources` has an additional meaning. Its secret is mounted for the +duration of each apt step in the generated Dockerfile and Dockerfile.buildbase, +so package installs resolve through the mirror the list names. It lands on +target when the spec sets one, and on +/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list otherwise. The first apt +step of Dockerfile.buildbase is deliberately excluded, because it installs +ca-certificates and a base image without them cannot complete a TLS handshake +with the mirror until that install finishes. When the secret is omitted the +generated Dockerfiles are unchanged and apt uses the distribution +repositories. + +#### Authenticating Clones From GitHub + +Source from several other repos is fetched during the build, as described +above. Those clones are unauthenticated by default, which is subject to +GitHub's rate limits and cannot reach a private repo. Set GITHUB_TOKEN in the +environment to authenticate them. + +```bash +$ export GITHUB_TOKEN= +$ ./build.py ... +``` + +This is not a build secret. The component and backend repos are cloned while +the build runs inside the container, not while an image is being built, so a +build secret cannot reach them. build.py instead configures a git credential +helper in the build base image and forwards the token to the build container +when the variable is set. The helper holds no credential of its own, only a +reference to GITHUB_TOKEN, so it is safe in an image layer, and it keeps the +token out of .git/config, which an authenticated clone URL would not. When +GITHUB_TOKEN is unset the clones stay unauthenticated. + #### Experimental: Build Presets > **Experimental.** This feature is gated behind the From 133660f727ce59c650f19461df43200703a183b7 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Tue, 25 Aug 2026 22:33:12 -0700 Subject: [PATCH 09/15] build: Mount the apt source list on every apt step The first apt step of the build base was excluded from the mount because it installs ca-certificates, and a base image without them cannot complete a TLS handshake with the mirror. That left the largest step of the build base going to the distribution repositories: in a pipeline run it fetched 45 packages from archive.ubuntu.com while the mounted step fetched 47 from the mirror. The exclusion is not needed. On a base image that already carries CA certificates the step resolves through the mirror like any other. On one that does not, apt reports the failed handshake, still exits 0, and installs from the distribution repositories, so the step behaves as it did before. Verified on a bare ubuntu image with no CA certificates: apt-get update exits 0 with certificate errors reported, ca-certificates installs from the distribution repositories, and the next step then resolves through the mirror. (cherry picked from commit 0ae883767dc04a8d95da2a16d6ec20742691438a) --- build.py | 33 +++++++++++++------------------ docs/customization_guide/build.md | 18 ++++++++++------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/build.py b/build.py index 0759ff5462..8cd1a00000 100755 --- a/build.py +++ b/build.py @@ -966,30 +966,25 @@ def apt_sources_secret_mount(): return "" -def mount_apt_sources_secret(df, skip=0): - """Prefix each 'RUN apt-get update' in df with the Artifactory source-list - secret mount, leaving the first 'skip' occurrences untouched. - - A secret mount is scoped to a single RUN instruction, so every apt block - that should resolve through Artifactory needs its own mount. The build base - skips its first block: that block bootstraps ca-certificates from the - distribution repositories, and a base image without CA certificates - (ubuntu:24.04 ships none) cannot complete a TLS handshake with Artifactory - until it has finished. +def mount_apt_sources_secret(df): + """Prefix every 'RUN apt-get update' in df with the apt source list mount. + + A secret mount is scoped to a single RUN instruction, so every apt step + that should resolve through the mirror needs its own. + + The first step of the build base is included even though it is the step + that installs ca-certificates. On a base image that already carries them + the step resolves through the mirror like any other. On one that does not, + apt reports the failed TLS handshake, still exits 0, and installs from the + distribution repositories, so the step behaves as it did before the mount + was added. """ mount = apt_sources_secret_mount() if not mount: return df marker = "RUN apt-get update" - chunks = df.split(marker) - result = chunks[0] - for i, chunk in enumerate(chunks[1:]): - if i < skip: - result += marker + chunk - else: - result += "RUN " + mount + "apt-get update" + chunk - return result + return df.replace(marker, "RUN " + mount + "apt-get update") def create_dockerfile_buildbase_rhel(ddir, dockerfile_name, argmap): @@ -1262,7 +1257,7 @@ def create_dockerfile_buildbase(ddir, dockerfile_name, argmap): ENTRYPOINT [] """ - df = mount_apt_sources_secret(df, skip=1) + df = mount_apt_sources_secret(df) with open(os.path.join(ddir, dockerfile_name), "w") as dfile: dfile.write(df) diff --git a/docs/customization_guide/build.md b/docs/customization_guide/build.md index e33693b392..8174b467a1 100644 --- a/docs/customization_guide/build.md +++ b/docs/customization_guide/build.md @@ -200,15 +200,19 @@ Dockerfile rather than the build command, so build.py removes it before invoking docker and applies it when generating the Dockerfile. The id `apt_sources` has an additional meaning. Its secret is mounted for the -duration of each apt step in the generated Dockerfile and Dockerfile.buildbase, +duration of every apt step in the generated Dockerfile and Dockerfile.buildbase, so package installs resolve through the mirror the list names. It lands on target when the spec sets one, and on -/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list otherwise. The first apt -step of Dockerfile.buildbase is deliberately excluded, because it installs -ca-certificates and a base image without them cannot complete a TLS handshake -with the mirror until that install finishes. When the secret is omitted the -generated Dockerfiles are unchanged and apt uses the distribution -repositories. +/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list otherwise. When the +secret is omitted the generated Dockerfiles are unchanged and apt uses the +distribution repositories. + +This includes the first step of Dockerfile.buildbase, which is also the step +that installs ca-certificates. On a base image that already carries them the +step resolves through the mirror like any other. On one that does not, apt +reports the failed TLS handshake, still exits 0, and installs from the +distribution repositories, so the step behaves as it did before the mount was +added. #### Authenticating Clones From GitHub From 68a6e590ed2211a3d68a571fb387306d28dedb15 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:27:33 -0700 Subject: [PATCH 10/15] build: Authenticate GitHub clones with a git config secret Replace the credential helper with a git config supplied as the 'gitconfig' build secret. It carries a url.insteadOf rewrite, which is the form the rest of the build tooling already uses, and it removes the shell function that had to be embedded in three Dockerfiles. The token has to be written into the config: git does not expand environment variables inside url.insteadOf, so a config that refers to one is stored literally and never authenticates. Point git at the config with GIT_CONFIG_GLOBAL rather than installing it at the default path, so only the steps that mount it are affected. Steps that clone while an image is built receive it as a build secret. cmake_build clones while the build runs inside the container, where a build secret cannot reach it, so the same file is bind mounted read only there instead. The config is never part of an image layer, and only its path reaches the generated build scripts, which CI publishes as an artifact. Export GIT_CONFIG_GLOBAL rather than using an assignment prefix. A prefix is a syntax error ahead of an 'if' keyword and does not survive '&&', so on four of the six steps it would either fail to parse or never reach the command that clones. --- Dockerfile.QA | 16 +++------ Dockerfile.sdk | 28 +++++----------- build.py | 55 ++++++++++++++++++------------- docs/customization_guide/build.md | 35 +++++++++++++------- 4 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Dockerfile.QA b/Dockerfile.QA index 2754180ea4..8354d9aeb4 100644 --- a/Dockerfile.QA +++ b/Dockerfile.QA @@ -176,19 +176,10 @@ RUN mkdir -p qa/custom_models/custom_sequence_int32/1 && \ cp tritonbuild/tritonserver/backends/dyna_sequence/libtriton_dyna_sequence.so \ qa/custom_models/custom_dyna_sequence_int32/1/. -# Authenticate the GitHub clones cmake performs for the component repos. The -# helper stores no credential, only a reference to GITHUB_TOKEN, so it is safe -# in a layer and in the pushed image. Each step that clones supplies the value -# with --mount=type=secret,id=github_token. The helper prints nothing when the -# token is absent, leaving git to clone anonymously as before. -RUN git config --global credential."https://github.com".helper \ - '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \ - echo username=x-access-token; \ - echo "password=${GITHUB_TOKEN}"; }; f' - # L0_lifecycle needs No-GPU build of identity backend. WORKDIR /workspace/tritonbuild/identity -RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ +RUN --mount=type=secret,id=gitconfig,target=/run/secrets/gitconfig,required=false \ + export GIT_CONFIG_GLOBAL=/run/secrets/gitconfig && \ rm -rf install build && mkdir build && cd build && \ cmake -DTRITON_ENABLE_GPU=OFF \ -DCMAKE_INSTALL_PREFIX:PATH=/workspace/tritonbuild/identity/install \ @@ -202,7 +193,8 @@ RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ # L0_backend_python test require triton_shm_monitor ARG TRITON_BOOST_URL="https://archives.boost.io/release/1.80.0/source/boost_1_80_0.tar.gz" WORKDIR /workspace/tritonbuild/python -RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ +RUN --mount=type=secret,id=gitconfig,target=/run/secrets/gitconfig,required=false \ + export GIT_CONFIG_GLOBAL=/run/secrets/gitconfig && \ rm -rf install build && mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX:PATH=/workspace/tritonbuild/python/install \ -DTRITON_REPO_ORGANIZATION:STRING=${TRITON_REPO_ORGANIZATION} \ diff --git a/Dockerfile.sdk b/Dockerfile.sdk index 3ae6263453..f8b23ab067 100644 --- a/Dockerfile.sdk +++ b/Dockerfile.sdk @@ -124,18 +124,9 @@ WORKDIR /workspace COPY TRITON_VERSION . COPY ${TRITON_CLIENT_REPO_SUBDIR} client -# Authenticate the GitHub clones cmake performs for the component repos. The -# helper stores no credential, only a reference to GITHUB_TOKEN, so it is safe -# in a layer and in the pushed image. Each step that clones supplies the value -# with --mount=type=secret,id=github_token. The helper prints nothing when the -# token is absent, leaving git to clone anonymously as before. -RUN git config --global credential."https://github.com".helper \ - '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \ - echo username=x-access-token; \ - echo "password=${GITHUB_TOKEN}"; }; f' - WORKDIR /workspace/client_build -RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ +RUN --mount=type=secret,id=gitconfig,target=/run/secrets/gitconfig,required=false \ + export GIT_CONFIG_GLOBAL=/run/secrets/gitconfig && \ cmake -DCMAKE_INSTALL_PREFIX=/workspace/install \ -DTRITON_VERSION=`cat /workspace/TRITON_VERSION` \ -DTRITON_REPO_ORGANIZATION=${TRITON_REPO_ORGANIZATION} \ @@ -149,13 +140,15 @@ RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ -DTRITON_ENABLE_EXAMPLES=ON -DTRITON_ENABLE_TESTS=ON \ -DTRITON_ENABLE_GPU=${TRITON_ENABLE_GPU} /workspace/client RUN --mount=type=secret,id=maven_settings,target=/run/secrets/maven_settings,required=false \ - --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ + --mount=type=secret,id=gitconfig,target=/run/secrets/gitconfig,required=false \ + export GIT_CONFIG_GLOBAL=/run/secrets/gitconfig && \ if [ -f /run/secrets/maven_settings ]; then export MAVEN_ARGS="--settings /run/secrets/maven_settings"; fi && \ cmake --build . -v --parallel ${TRITON_CLIENT_BUILD_PARALLEL} --target cc-clients java-clients python-clients # Install Java API Bindings RUN --mount=type=secret,id=maven_settings,target=/run/secrets/maven_settings,required=false \ - --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ + --mount=type=secret,id=gitconfig,target=/run/secrets/gitconfig,required=false \ + export GIT_CONFIG_GLOBAL=/run/secrets/gitconfig && \ if [ -f /run/secrets/maven_settings ]; then export MAVEN_ARGS="--settings /run/secrets/maven_settings"; fi && \ if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ source /workspace/client/src/java-api-bindings/scripts/install_dependencies_and_build.sh \ @@ -271,13 +264,8 @@ RUN rm -f /usr/bin/python && \ # Install Model Analyzer ARG TRITON_MODEL_ANALYZER_REPO_TAG ARG TRITON_MODEL_ANALYZER_REPO="${TRITON_REPO_ORGANIZATION}/model_analyzer@${TRITON_MODEL_ANALYZER_REPO_TAG}" -# Separate stage from the client build, so the credential helper is configured -# again here. It stores no credential, only a reference to GITHUB_TOKEN. -RUN git config --global credential."https://github.com".helper \ - '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \ - echo username=x-access-token; \ - echo "password=${GITHUB_TOKEN}"; }; f' -RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ +RUN --mount=type=secret,id=gitconfig,target=/run/secrets/gitconfig,required=false \ + export GIT_CONFIG_GLOBAL=/run/secrets/gitconfig && \ pip3 install "git+${TRITON_MODEL_ANALYZER_REPO}" # Entrypoint Banner diff --git a/build.py b/build.py index 8cd1a00000..1a814a8995 100755 --- a/build.py +++ b/build.py @@ -888,6 +888,12 @@ def install_dcgm_libraries(dcgm_version, target_machine): APT_SOURCES_SECRET_ID = "apt_sources" APT_SOURCES_DEFAULT_TARGET = "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list" +# Id whose secret is a git config authenticating GitHub, and where it is mounted. +# git is pointed at it with GIT_CONFIG_GLOBAL rather than by installing it at the +# default path, so nothing outside the step that mounts it picks it up. +GIT_CONFIG_SECRET_ID = "gitconfig" +GIT_CONFIG_TARGET = "/run/secrets/gitconfig" + def parse_secret_spec(spec): """Split a Docker secret spec into ordered (key, value) pairs. @@ -927,6 +933,18 @@ def declared_secret_ids(): return {i for i in (secret_spec_id(s) for s in declared_secret_specs()) if i} +def secret_source_path(secret_id): + """Return the host path a declared secret reads from, or "" when it has none. + + Only a file-backed secret has a path. One sourced from the environment has + nothing to bind into a container, so callers that need a file skip it. + """ + for spec in declared_secret_specs(): + if secret_spec_id(spec) == secret_id: + return secret_spec_value(spec, "src") or secret_spec_value(spec, "source") + return "" + + def secret_build_args(): """Return the '--secret' arguments to forward to 'docker build'. @@ -1231,23 +1249,6 @@ def create_dockerfile_buildbase(ddir, dockerfile_name, argmap): os.getenv("CCACHE_REMOTE_STORAGE") ) - # Authenticate GitHub clones. cmake_build clones the component and backend - # repositories while running inside this container, not while the image is - # being built, so a docker build secret cannot reach it. Configure a - # credential helper instead: it holds no credential itself, only a - # reference to GITHUB_TOKEN, so it is safe in a layer and in the pushed - # build base image. The value is supplied by 'docker run -e GITHUB_TOKEN'. - # - # The helper exits without printing anything when GITHUB_TOKEN is empty, - # which leaves git to fall back to unauthenticated access rather than - # offering an empty password and failing a clone that used to work. - df += """ -RUN git config --global credential."https://github.com".helper \\ - '!f() { test -n "${GITHUB_TOKEN}" || exit 0; \\ - echo username=x-access-token; \\ - echo "password=${GITHUB_TOKEN}"; }; f' -""" - # Copy in the triton source. We remove existing contents first in # case the FROM container has something there already. df += """ @@ -1861,12 +1862,20 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ # or explicit --release-version). Dev / pre-release builds leave it # unset so build_wheel.py reads the in-tree TRITON_VERSION file and # takes the PEP 817 variant path. - # Forward the GitHub token to the clones cmake_build performs in this - # container. Passed by name rather than as NAME=value: 'docker run -e - # VAR' takes the value from the ambient environment, so the token stays - # out of this script, which CI publishes as a build artifact. - if os.environ.get("GITHUB_TOKEN"): - runargs += ["-e", "GITHUB_TOKEN"] + # Authenticate the GitHub clones cmake_build performs in this container. + # It runs the build rather than an image build, so a build secret cannot + # reach it: bind the same git config read only instead, and point git at + # it by environment rather than installing it at the default path. Only + # the host path reaches this script, which CI publishes as a build + # artifact, so the credential the config carries stays out of it. + git_config_src = secret_source_path(GIT_CONFIG_SECRET_ID) + if git_config_src: + runargs += [ + "-v", + "{}:{}:ro".format(git_config_src, GIT_CONFIG_TARGET), + "-e", + "GIT_CONFIG_GLOBAL={}".format(GIT_CONFIG_TARGET), + ] if "TRITON_RELEASE_VERSION" in os.environ: runargs += [ diff --git a/docs/customization_guide/build.md b/docs/customization_guide/build.md index 8174b467a1..fed049dc4f 100644 --- a/docs/customization_guide/build.md +++ b/docs/customization_guide/build.md @@ -218,22 +218,33 @@ added. Source from several other repos is fetched during the build, as described above. Those clones are unauthenticated by default, which is subject to -GitHub's rate limits and cannot reach a private repo. Set GITHUB_TOKEN in the -environment to authenticate them. +GitHub's rate limits and cannot reach a private repo. Supply a git config that +rewrites the GitHub URL to an authenticated one, as the `gitconfig` build +secret. ```bash -$ export GITHUB_TOKEN= -$ ./build.py ... +$ cat > /tmp/gitconfig <@github.com/"] + insteadOf = https://github.com/ +EOF +$ chmod 600 /tmp/gitconfig +$ ./build.py ... --docker-build-secret id=gitconfig,src=/tmp/gitconfig ``` -This is not a build secret. The component and backend repos are cloned while -the build runs inside the container, not while an image is being built, so a -build secret cannot reach them. build.py instead configures a git credential -helper in the build base image and forwards the token to the build container -when the variable is set. The helper holds no credential of its own, only a -reference to GITHUB_TOKEN, so it is safe in an image layer, and it keeps the -token out of .git/config, which an authenticated clone URL would not. When -GITHUB_TOKEN is unset the clones stay unauthenticated. +The token has to appear in the file. git does not expand environment variables +inside `url..insteadOf`, so a config that refers to one is stored +literally and silently fails to authenticate. + +build.py mounts the config on each step that clones and points git at it with +GIT_CONFIG_GLOBAL rather than installing it at the default path, so nothing +outside those steps picks it up. Steps that run while an image is being built +receive it as a build secret. cmake_build clones while the build runs inside +the container rather than while an image is built, where a build secret cannot +reach it, so there the same file is bind mounted read only instead. Either way +the config is never part of an image layer, and only its path reaches the +generated build scripts. + +When the secret is omitted the clones stay unauthenticated. #### Experimental: Build Presets From dd2f3884cdf1ea0a6cac8202ff471c7bcb9be0ea Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:43:18 -0700 Subject: [PATCH 11/15] fix: Configure git from the environment for the in-container build The build container was given the git config as a read only bind mount, which does not work: docker resolves the source path on the daemon rather than in the job, and creates a directory when it does not find one. git then refused to read its configuration at all and every clone failed. warning: unable to access '/run/secrets/gitconfig': Is a directory fatal: unknown error occurred while reading the configuration files Read the configuration from the environment instead, which crosses the daemon boundary because it needs no filesystem. The variables are forwarded by name rather than as NAME=value, so the one carrying the credential stays out of the generated build scripts, which CI publishes as an artifact. The image builds are unaffected and keep the git config secret: 'docker build --secret' reads the source file on the client, so it never crossed that boundary. --- build.py | 44 +++++++++++-------------------- docs/customization_guide/build.md | 23 +++++++++++----- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/build.py b/build.py index 1a814a8995..7e9474b0a3 100755 --- a/build.py +++ b/build.py @@ -888,11 +888,10 @@ def install_dcgm_libraries(dcgm_version, target_machine): APT_SOURCES_SECRET_ID = "apt_sources" APT_SOURCES_DEFAULT_TARGET = "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list" -# Id whose secret is a git config authenticating GitHub, and where it is mounted. -# git is pointed at it with GIT_CONFIG_GLOBAL rather than by installing it at the -# default path, so nothing outside the step that mounts it picks it up. -GIT_CONFIG_SECRET_ID = "gitconfig" -GIT_CONFIG_TARGET = "/run/secrets/gitconfig" +# Variables through which git reads configuration straight from the environment. +# The build container is given these so its clones authenticate without a file, +# which a bind mount could not deliver across the docker daemon boundary. +GIT_CONFIG_ENV_VARS = ("GIT_CONFIG_COUNT", "GIT_CONFIG_KEY_0", "GIT_CONFIG_VALUE_0") def parse_secret_spec(spec): @@ -933,18 +932,6 @@ def declared_secret_ids(): return {i for i in (secret_spec_id(s) for s in declared_secret_specs()) if i} -def secret_source_path(secret_id): - """Return the host path a declared secret reads from, or "" when it has none. - - Only a file-backed secret has a path. One sourced from the environment has - nothing to bind into a container, so callers that need a file skip it. - """ - for spec in declared_secret_specs(): - if secret_spec_id(spec) == secret_id: - return secret_spec_value(spec, "src") or secret_spec_value(spec, "source") - return "" - - def secret_build_args(): """Return the '--secret' arguments to forward to 'docker build'. @@ -1864,18 +1851,17 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ # takes the PEP 817 variant path. # Authenticate the GitHub clones cmake_build performs in this container. # It runs the build rather than an image build, so a build secret cannot - # reach it: bind the same git config read only instead, and point git at - # it by environment rather than installing it at the default path. Only - # the host path reaches this script, which CI publishes as a build - # artifact, so the credential the config carries stays out of it. - git_config_src = secret_source_path(GIT_CONFIG_SECRET_ID) - if git_config_src: - runargs += [ - "-v", - "{}:{}:ro".format(git_config_src, GIT_CONFIG_TARGET), - "-e", - "GIT_CONFIG_GLOBAL={}".format(GIT_CONFIG_TARGET), - ] + # reach it, and a bind mount cannot either: docker resolves the source + # path on the daemon, which need not share the filesystem this script + # runs on, and silently creates a directory when it does not find it. + # git reads configuration straight from the environment instead. + # + # Passed by name rather than as NAME=value, so the values, one of which + # carries the credential, stay out of this script, which CI publishes as + # a build artifact. + for var in GIT_CONFIG_ENV_VARS: + if os.environ.get(var): + runargs += ["-e", var] if "TRITON_RELEASE_VERSION" in os.environ: runargs += [ diff --git a/docs/customization_guide/build.md b/docs/customization_guide/build.md index fed049dc4f..59be777036 100644 --- a/docs/customization_guide/build.md +++ b/docs/customization_guide/build.md @@ -237,12 +237,23 @@ literally and silently fails to authenticate. build.py mounts the config on each step that clones and points git at it with GIT_CONFIG_GLOBAL rather than installing it at the default path, so nothing -outside those steps picks it up. Steps that run while an image is being built -receive it as a build secret. cmake_build clones while the build runs inside -the container rather than while an image is built, where a build secret cannot -reach it, so there the same file is bind mounted read only instead. Either way -the config is never part of an image layer, and only its path reaches the -generated build scripts. +outside those steps picks it up. The config is never part of an image layer, +and only its path reaches the generated build scripts. + +cmake_build clones while the build runs inside the container rather than while +an image is built, so no build secret reaches it, and neither does a bind mount: +docker resolves the source path on the daemon, which need not share the +filesystem build.py runs on, and silently creates a directory when it does not +find it. Set the configuration in the environment for that case instead, and +build.py forwards the variables to the build container by name, keeping the +value that carries the token out of the generated build scripts. + +```bash +$ export GIT_CONFIG_COUNT=1 +$ export GIT_CONFIG_KEY_0="url.https://x-access-token:@github.com/.insteadOf" +$ export GIT_CONFIG_VALUE_0="https://github.com/" +$ ./build.py ... +``` When the secret is omitted the clones stay unauthenticated. From fca8a41233cab15ce36b04f3e938f15caaf51a50 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:48:45 -0700 Subject: [PATCH 12/15] build: Bind the git config into the build container again Restores the read only bind mount and GIT_CONFIG_GLOBAL for the container that runs the build, in place of reading the configuration from the environment. The mount failed before only because the config was written under /tmp, which the docker daemon does not see when the runner has a private one, leaving it to create a directory in place of the file. Writing the config somewhere the daemon shares fixes that, and keeps one mechanism across every step that clones. --- build.py | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/build.py b/build.py index 7e9474b0a3..1137c83026 100755 --- a/build.py +++ b/build.py @@ -888,10 +888,11 @@ def install_dcgm_libraries(dcgm_version, target_machine): APT_SOURCES_SECRET_ID = "apt_sources" APT_SOURCES_DEFAULT_TARGET = "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list" -# Variables through which git reads configuration straight from the environment. -# The build container is given these so its clones authenticate without a file, -# which a bind mount could not deliver across the docker daemon boundary. -GIT_CONFIG_ENV_VARS = ("GIT_CONFIG_COUNT", "GIT_CONFIG_KEY_0", "GIT_CONFIG_VALUE_0") +# Id whose secret is a git config authenticating GitHub, and where it is mounted. +# git is pointed at it with GIT_CONFIG_GLOBAL rather than by installing it at the +# default path, so nothing outside the step that mounts it picks it up. +GIT_CONFIG_SECRET_ID = "gitconfig" +GIT_CONFIG_TARGET = "/run/secrets/gitconfig" def parse_secret_spec(spec): @@ -932,6 +933,18 @@ def declared_secret_ids(): return {i for i in (secret_spec_id(s) for s in declared_secret_specs()) if i} +def secret_source_path(secret_id): + """Return the host path a declared secret reads from, or "" when it has none. + + Only a file-backed secret has a path. One sourced from the environment has + nothing to bind into a container, so callers that need a file skip it. + """ + for spec in declared_secret_specs(): + if secret_spec_id(spec) == secret_id: + return secret_spec_value(spec, "src") or secret_spec_value(spec, "source") + return "" + + def secret_build_args(): """Return the '--secret' arguments to forward to 'docker build'. @@ -1851,17 +1864,23 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ # takes the PEP 817 variant path. # Authenticate the GitHub clones cmake_build performs in this container. # It runs the build rather than an image build, so a build secret cannot - # reach it, and a bind mount cannot either: docker resolves the source - # path on the daemon, which need not share the filesystem this script - # runs on, and silently creates a directory when it does not find it. - # git reads configuration straight from the environment instead. + # reach it: bind the same git config read only instead, and point git at + # it by environment rather than installing it at the default path. Only + # the path reaches this script, which CI publishes as a build artifact, + # so the credential the config carries stays out of it. # - # Passed by name rather than as NAME=value, so the values, one of which - # carries the credential, stay out of this script, which CI publishes as - # a build artifact. - for var in GIT_CONFIG_ENV_VARS: - if os.environ.get(var): - runargs += ["-e", var] + # docker resolves the source path on the daemon rather than here, so the + # config has to sit somewhere the daemon also sees. A path under /tmp is + # not such a place when the runner has a private one: docker finds no + # file and silently mounts a new directory in its place. + git_config_src = secret_source_path(GIT_CONFIG_SECRET_ID) + if git_config_src: + runargs += [ + "-v", + "{}:{}:ro".format(git_config_src, GIT_CONFIG_TARGET), + "-e", + "GIT_CONFIG_GLOBAL={}".format(GIT_CONFIG_TARGET), + ] if "TRITON_RELEASE_VERSION" in os.environ: runargs += [ From c2a03db8d49e3750985b40a4e57d9495f5d2e95c Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:12:10 -0700 Subject: [PATCH 13/15] fix: Configure git from the environment for the in-container build Binding the git config into the build container does not work on this runner, whichever directory the file is written to. Docker resolves the source path on the daemon, which shares neither /tmp nor the project directory with the job, so it finds no file and mounts a new directory in its place. git then refuses to read any configuration and every clone fails. warning: unable to access '/run/secrets/gitconfig': Is a directory fatal: unknown error occurred while reading the configuration files Read the configuration from the environment instead, which needs no filesystem and so crosses that boundary. The variables are forwarded by name rather than as NAME=value, keeping the one that carries the credential out of the generated build scripts, which CI publishes as an artifact. The image builds keep the git config secret. 'docker build --secret' reads the source file on the client, so it never crossed the boundary: the same job that failed here built its images from that file without trouble. --- build.py | 47 ++++++++++++++--------------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/build.py b/build.py index 1137c83026..7e9474b0a3 100755 --- a/build.py +++ b/build.py @@ -888,11 +888,10 @@ def install_dcgm_libraries(dcgm_version, target_machine): APT_SOURCES_SECRET_ID = "apt_sources" APT_SOURCES_DEFAULT_TARGET = "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list" -# Id whose secret is a git config authenticating GitHub, and where it is mounted. -# git is pointed at it with GIT_CONFIG_GLOBAL rather than by installing it at the -# default path, so nothing outside the step that mounts it picks it up. -GIT_CONFIG_SECRET_ID = "gitconfig" -GIT_CONFIG_TARGET = "/run/secrets/gitconfig" +# Variables through which git reads configuration straight from the environment. +# The build container is given these so its clones authenticate without a file, +# which a bind mount could not deliver across the docker daemon boundary. +GIT_CONFIG_ENV_VARS = ("GIT_CONFIG_COUNT", "GIT_CONFIG_KEY_0", "GIT_CONFIG_VALUE_0") def parse_secret_spec(spec): @@ -933,18 +932,6 @@ def declared_secret_ids(): return {i for i in (secret_spec_id(s) for s in declared_secret_specs()) if i} -def secret_source_path(secret_id): - """Return the host path a declared secret reads from, or "" when it has none. - - Only a file-backed secret has a path. One sourced from the environment has - nothing to bind into a container, so callers that need a file skip it. - """ - for spec in declared_secret_specs(): - if secret_spec_id(spec) == secret_id: - return secret_spec_value(spec, "src") or secret_spec_value(spec, "source") - return "" - - def secret_build_args(): """Return the '--secret' arguments to forward to 'docker build'. @@ -1864,23 +1851,17 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ # takes the PEP 817 variant path. # Authenticate the GitHub clones cmake_build performs in this container. # It runs the build rather than an image build, so a build secret cannot - # reach it: bind the same git config read only instead, and point git at - # it by environment rather than installing it at the default path. Only - # the path reaches this script, which CI publishes as a build artifact, - # so the credential the config carries stays out of it. + # reach it, and a bind mount cannot either: docker resolves the source + # path on the daemon, which need not share the filesystem this script + # runs on, and silently creates a directory when it does not find it. + # git reads configuration straight from the environment instead. # - # docker resolves the source path on the daemon rather than here, so the - # config has to sit somewhere the daemon also sees. A path under /tmp is - # not such a place when the runner has a private one: docker finds no - # file and silently mounts a new directory in its place. - git_config_src = secret_source_path(GIT_CONFIG_SECRET_ID) - if git_config_src: - runargs += [ - "-v", - "{}:{}:ro".format(git_config_src, GIT_CONFIG_TARGET), - "-e", - "GIT_CONFIG_GLOBAL={}".format(GIT_CONFIG_TARGET), - ] + # Passed by name rather than as NAME=value, so the values, one of which + # carries the credential, stay out of this script, which CI publishes as + # a build artifact. + for var in GIT_CONFIG_ENV_VARS: + if os.environ.get(var): + runargs += ["-e", var] if "TRITON_RELEASE_VERSION" in os.environ: runargs += [ From c419b565613ae73af6d73092d55640b4f39da86e Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:30:30 -0700 Subject: [PATCH 14/15] build: Write the git config inside the container from its contents The container build.py runs the build in cannot be given a file. Docker resolves a bind mount source on the daemon, which shares neither /tmp nor the project directory with the job, so it mounts a directory in place of the config and git refuses to read any configuration. Hand the container the contents instead and let it write the file there, then point git at it for that command only. The variable is forwarded by name rather than as NAME=value, so the contents, which carry a credential, stay out of the generated build scripts, which CI publishes as an artifact. Without the variable the container command is unchanged and clones stay unauthenticated. --- build.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/build.py b/build.py index 7e9474b0a3..4a2fad94db 100755 --- a/build.py +++ b/build.py @@ -888,10 +888,12 @@ def install_dcgm_libraries(dcgm_version, target_machine): APT_SOURCES_SECRET_ID = "apt_sources" APT_SOURCES_DEFAULT_TARGET = "/etc/apt/sources.list.d/nvidia-artifactory-ubuntu.list" -# Variables through which git reads configuration straight from the environment. -# The build container is given these so its clones authenticate without a file, -# which a bind mount could not deliver across the docker daemon boundary. -GIT_CONFIG_ENV_VARS = ("GIT_CONFIG_COUNT", "GIT_CONFIG_KEY_0", "GIT_CONFIG_VALUE_0") +# Variable holding a git config, and where the build container writes it out. +# The container is handed the contents rather than the file because docker +# resolves a bind mount source on the daemon, which need not share a filesystem +# with this script, and silently mounts a directory when it finds nothing there. +GIT_CONFIG_CONTENT_ENV = "TRITON_GITCONFIG" +GIT_CONFIG_CONTAINER_PATH = "/tmp/gitconfig" def parse_secret_spec(spec): @@ -1854,14 +1856,13 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ # reach it, and a bind mount cannot either: docker resolves the source # path on the daemon, which need not share the filesystem this script # runs on, and silently creates a directory when it does not find it. - # git reads configuration straight from the environment instead. + # Hand the container the contents instead and let it write the file. # - # Passed by name rather than as NAME=value, so the values, one of which - # carries the credential, stay out of this script, which CI publishes as - # a build artifact. - for var in GIT_CONFIG_ENV_VARS: - if os.environ.get(var): - runargs += ["-e", var] + # Passed by name rather than as NAME=value, so the contents, which carry + # a credential, stay out of this script, which CI publishes as a build + # artifact. + if os.environ.get(GIT_CONFIG_CONTENT_ENV): + runargs += ["-e", GIT_CONFIG_CONTENT_ENV] if "TRITON_RELEASE_VERSION" in os.environ: runargs += [ @@ -1879,7 +1880,23 @@ def create_docker_build_script(script_name, container_install_dir, container_ci_ runargs += ["tritonserver_buildbase"] - runargs += ["./cmake_build"] + # Write the git config out inside the container, where the contents + # arrived as an environment variable, and point git at it for the build + # only. Single quoted so the outer shell leaves the expansion to the + # container, whose environment is the one holding the value. + if os.environ.get(GIT_CONFIG_CONTENT_ENV): + runargs += [ + "bash", + "-c", + '\'printf "%s" "${}" > {} && export GIT_CONFIG_GLOBAL={} && ' + "./cmake_build'".format( + GIT_CONFIG_CONTENT_ENV, + GIT_CONFIG_CONTAINER_PATH, + GIT_CONFIG_CONTAINER_PATH, + ), + ] + else: + runargs += ["./cmake_build"] # Remove existing tritonserver_builder container... docker_script._file.write( From 4045b10508984507db7d1bf4bfd40d0613ef1b37 Mon Sep 17 00:00:00 2001 From: "M. Chornyi" <99709299+mc-nv@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:42:53 -0700 Subject: [PATCH 15/15] docs: Describe how the git config reaches each part of the build The GitHub authentication section still described variables that no longer exist. Replace them with TRITON_GITCONFIG, and say plainly that the two mechanisms cover different parts of the build: the secret reaches the steps that clone while an image is built, the variable reaches the build itself, and a build wanting authenticated clones throughout sets both. --- docs/customization_guide/build.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/customization_guide/build.md b/docs/customization_guide/build.md index 59be777036..0e1ca25908 100644 --- a/docs/customization_guide/build.md +++ b/docs/customization_guide/build.md @@ -235,27 +235,28 @@ The token has to appear in the file. git does not expand environment variables inside `url..insteadOf`, so a config that refers to one is stored literally and silently fails to authenticate. -build.py mounts the config on each step that clones and points git at it with -GIT_CONFIG_GLOBAL rather than installing it at the default path, so nothing -outside those steps picks it up. The config is never part of an image layer, -and only its path reaches the generated build scripts. +That covers the steps that clone while an image is being built. build.py mounts +the config on each of them and points git at it with GIT_CONFIG_GLOBAL rather +than installing it at the default path, so nothing outside those steps picks it +up. The config is never part of an image layer, and only its path reaches the +generated build scripts. cmake_build clones while the build runs inside the container rather than while -an image is built, so no build secret reaches it, and neither does a bind mount: -docker resolves the source path on the daemon, which need not share the -filesystem build.py runs on, and silently creates a directory when it does not -find it. Set the configuration in the environment for that case instead, and -build.py forwards the variables to the build container by name, keeping the -value that carries the token out of the generated build scripts. +an image is built, so no build secret reaches it. Neither does a bind mount: +docker resolves the source path on the daemon, which need not share a filesystem +with build.py, and silently mounts a directory when it finds nothing there. Put +the same text in TRITON_GITCONFIG for that case. build.py forwards the variable +to the container by name and has it write the file there, so the contents stay +out of the generated build scripts. ```bash -$ export GIT_CONFIG_COUNT=1 -$ export GIT_CONFIG_KEY_0="url.https://x-access-token:@github.com/.insteadOf" -$ export GIT_CONFIG_VALUE_0="https://github.com/" +$ export TRITON_GITCONFIG="$(cat /tmp/gitconfig)" $ ./build.py ... ``` -When the secret is omitted the clones stay unauthenticated. +Set both when a build needs authenticated clones throughout: the secret for the +image builds, the variable for the build itself. Omit either and the steps it +covers clone unauthenticated. #### Experimental: Build Presets