Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Custom [dev container Features](https://containers.dev/implementors/features/) f
| [Microsoft Security DevOps CLI](src/microsoft-security-devops-cli) | `guardian` CLI for running security analysis tools without .NET |
| [Copilot CLI Persistence](src/copilot-persistence) | Persist GitHub Copilot CLI settings & chat history across rebuilds |
| [Atlassian CLI for Jira & Confluence](src/atlassian-jira-confluence-cli) | `acli` for working with Jira and Confluence Cloud |
| [Package Source Overrides](src/package-source-overrides) | Point npm, pnpm, pip, and NuGet at internal override feeds |
| [Package Source Overrides](src/package-source-overrides) | Point npm, pnpm, Yarn, Corepack, pip, uv, and NuGet at internal override feeds |
| [GitLab CI Local](src/gitlab-ci-local) | Run GitLab CI/CD pipelines locally with `gitlab-ci-local` |
| [Prompty Dumpty](src/prompty-dumpty) | `dumpty` CLI for managing prompty files |

Expand Down Expand Up @@ -158,7 +158,7 @@ acli jira project list

### Package Source Overrides

Points npm, pnpm, pip, and NuGet at internal **override package sources** (a pull-through proxy such as Artifactory, Nexus, or Azure Artifacts) instead of public feeds. It writes system- and user-scoped configuration as early as possible in the container lifecycle so that other Features also resolve packages through the proxy during their own installation.
Points npm, pnpm, Yarn, Corepack, pip, uv, and NuGet at internal **override package sources** (a pull-through proxy such as Artifactory, Nexus, or Azure Artifacts) instead of public feeds. It writes system- and user-scoped configuration so that Features installed after it, and your projects, resolve packages through the proxy.

**Usage:**

Expand All @@ -180,16 +180,18 @@ Points npm, pnpm, pip, and NuGet at internal **override package sources** (a pul
```

**Options:**
- `npmRegistry` - registry URL for npm and pnpm (default: "")
- `pipIndexUrl` - pip / PyPI index URL that replaces the default index (default: "")
- `npmRegistry` - registry URL for npm, pnpm, Yarn (Classic and Berry), and Corepack (default: "")
- `pipIndexUrl` - PyPI index URL for pip and uv that replaces the default index (default: "")
- `uvPythonInstallMirror` - mirror of the python-build-standalone releases for uv-managed Python downloads (default: "")
- `nugetSource` - NuGet v3 source URL that replaces nuget.org (default: "")
- `nugetSourceName` - key/name for the configured NuGet source (default: "override")
- `scope` - where config is written: `system`, `user`, or `both` (default: "both")
- `strictSsl` - set to `false` for self-signed / HTTP internal proxies (default: `true`)
- `bashEnvHook` - opt-in `BASH_ENV` hook so later Features' install scripts see env-var-only overrides such as `COREPACK_NPM_REGISTRY` (default: `false`)

**Lifecycle & Ordering:**
**Install Order:**

The feature declares no `installsAfter` dependencies so it installs as early as possible, and it writes root-readable config (`/etc/pip.conf`, `/etc/npmrc`, `/root/.npmrc`, root `NuGet.Config`) that other Features' build-time (root) package installs pick up automatically. Because a Feature can't force itself ahead of arbitrary third-party Features, list it first in `overrideFeatureInstallOrder` to guarantee it runs before anything that downloads packages. Providing no URLs makes it a safe no-op.
Features install in rounds, and within a round the spec sorts them alphabetically by registry path, so `ghcr.io/devcontainers/*` Features install *before* this one unless you change the order. Per repository, list it first in `overrideFeatureInstallOrder` (as above). Machine-wide via VS Code's `dev.containers.defaultFeatures` there is no global override order; add `ghcr.io/devcontainers/features/common-utils:2` to the defaults too, because the official Features declare `installsAfter: common-utils` and therefore move after this Feature. See the [Feature docs](src/package-source-overrides) for details and caveats. Providing no URLs makes it a safe no-op.

### GitLab CI Local

Expand Down
146 changes: 114 additions & 32 deletions src/package-source-overrides/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,65 @@ In locked-down environments you often cannot reach public package feeds
internal pull-through proxy / mirror such as **JFrog Artifactory**, **Sonatype
Nexus**, or **Azure Artifacts**.

This Feature points **npm**, **pnpm**, **pip**, and **NuGet** at those override
sources by writing their configuration files as early as possible in the
container lifecycle, so that **other Features installed afterward also resolve
their packages through the proxy** during their own install scripts.
This Feature points **npm**, **pnpm**, **Yarn**, **Corepack**, **pip**, **uv**,
and **NuGet** at those override sources by writing their configuration, so that
**Features installed after it, and your projects, resolve packages through the
proxy**.

It only writes configuration — no network calls, no `apt`, and no assumption that
the package managers are already installed. Providing no URLs makes the Feature a
safe no-op.

## How It Works

For each ecosystem you give a URL, the Feature writes standard config files at
both **system** and **per-user** scope (`scope` option, default `both`). Per-user
config is written for **root** (so package installs run by other Features at build
time use the proxy) and for the **remote user** (so it also applies at runtime).
For each URL you provide, the Feature writes the standard config files for every
tool that consumes that feed. Per-user config is written for **root** (package
installs run by other Features at build time run as root) and for the **remote
user** (runtime).

| Ecosystem | System scope | User scope (root + remote user) |
|-----------|--------------|---------------------------------|
| npm / pnpm | `/etc/npmrc` (read via the `NPM_CONFIG_GLOBALCONFIG` env var this Feature sets) | `~/.npmrc` |
| pip / PyPI | `/etc/pip.conf` | `~/.config/pip/pip.conf` |
| NuGet | `/etc/opt/NuGet/Config/NuGet.Config` | `~/.nuget/NuGet/NuGet.Config` |
| Option | Tool | System scope | User scope (root + remote user) |
|--------|------|--------------|---------------------------------|
| `npmRegistry` | npm | `/etc/npmrc` (via the `NPM_CONFIG_GLOBALCONFIG` env var this Feature sets) | `~/.npmrc` |
| `npmRegistry` | pnpm, Yarn Classic | — (no system-wide config) | `~/.npmrc` (always written) |
| `npmRegistry` | Yarn Berry (v2+) | — (no system-wide config) | `~/.yarnrc.yml` `npmRegistryServer` (always written) |
| `npmRegistry` | Corepack | `COREPACK_NPM_REGISTRY` env var (see below) | — |
| `pipIndexUrl` | pip, pipx | `/etc/pip.conf` | `~/.config/pip/pip.conf` |
| `pipIndexUrl` | uv | `/etc/uv/uv.toml` | `~/.config/uv/uv.toml` |
| `uvPythonInstallMirror` | uv-managed Python downloads | `/etc/uv/uv.toml` `python-install-mirror` | `~/.config/uv/uv.toml` |
| `nugetSource` | NuGet / dotnet | `/etc/opt/NuGet/Config/NuGet.Config` | `~/.nuget/NuGet/NuGet.Config` |

- **npm / pnpm** share the same `npmRegistry` (pnpm reads `.npmrc`). The
`registry=` key replaces the default registry.
- **pip** sets `index-url`, which replaces the default PyPI index.
- **NuGet** writes `<clear />` before the override source, so `nuget.org` is
removed and only your source remains.
- `scope` (`system`, `user`, `both`; default `both`) selects the columns above.
pnpm and Yarn Berry have no system-wide config location, so their per-user
files are written for every scope.
- **npm / pnpm / Yarn Classic** — `registry=` replaces the default registry.
- **Yarn Berry** ignores `.npmrc`; it reads `npmRegistryServer` from `~/.yarnrc.yml`.
- **pip** — `index-url` replaces the default PyPI index.
- **uv** ignores `pip.conf` and `PIP_INDEX_URL`; it uses an `[[index]]` with
`default = true`. uv rejects unknown keys in `uv.toml`, so the file is kept
minimal.
- **uv-managed Python** (`uv python install`, automatic downloads) normally comes
from GitHub. Set `uvPythonInstallMirror` to a mirror of the
`python-build-standalone` release downloads.
- **NuGet** — `<clear />` removes inherited sources, and `nuget.org` is also
listed under `<disabledPackageSources>` so it stays off even when a
higher-priority config (such as the .NET SDK's default user config) re-adds it.

The `.npmrc` files are updated inside a managed marker block so any unrelated npm
settings you already have are preserved; `pip.conf` and `NuGet.Config` are fully
managed by this Feature.
`.npmrc` and `.yarnrc.yml` are updated inside a managed marker block so unrelated
settings are preserved (an existing `~/.yarnrc.yml` that already sets the
registry is left unchanged). `pip.conf`, `uv.toml`, and `NuGet.Config` are fully
managed by this Feature. Project-level configuration (a repository's own
`.npmrc`, `.yarnrc.yml`, `uv.toml`, `NuGet.Config`, …) still takes precedence.

## Getting It First in the Lifecycle
## Install order

A Feature cannot *force* itself to install before arbitrary third-party Features.
To maximize how early this runs, it declares **no `installsAfter` dependencies**,
so it sorts as early as the resolver allows, and it writes config to root-readable
default locations that later Features' package managers pick up automatically.
Dev container Features install in rounds; within a round the spec sorts Features
**alphabetically by their full registry path**. Features from
`ghcr.io/devcontainers/…` therefore install *before*
`ghcr.io/rosstaco/…`, and any of them that download packages (for example the
Python Feature installing its default tools with pip) would hit the public feeds
first. Make this Feature install first in one of these ways:

For a **hard guarantee** that it runs before Features that download packages, list
it first in `overrideFeatureInstallOrder`:
### Per repository: `overrideFeatureInstallOrder`

```jsonc
{
Expand All @@ -58,13 +76,73 @@ it first in `overrideFeatureInstallOrder`:
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/python:1": {}
},
// Ensure the override sources are configured before anything installs packages
// Configure the override sources before anything installs packages
"overrideFeatureInstallOrder": [
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides"
]
}
```

### Machine-wide: VS Code `dev.containers.defaultFeatures`

Adding this Feature to the VS Code user setting `dev.containers.defaultFeatures`
applies it to every dev container you open locally, but there is no global
equivalent of `overrideFeatureInstallOrder`, so on its own it installs *after*
the official Features. Add `common-utils` to the default Features as well: the
official Features (node, python, dotnet, java, go, rust, ruby, conda, powershell,
docker-in-docker, azure-cli, github-cli, …) declare
`installsAfter: common-utils`, which moves them to a later round — after this
Feature, which deliberately declares no `installsAfter`.

```jsonc
// VS Code user settings.json
"dev.containers.defaultFeatures": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": false,
"installOhMyZsh": false,
"upgradePackages": false,
"username": "none"
},
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides:1": {
"npmRegistry": "https://artifactory.example.com/artifactory/api/npm/npm-remote/",
"pipIndexUrl": "https://artifactory.example.com/artifactory/api/pypi/pypi-remote/simple",
"nugetSource": "https://artifactory.example.com/artifactory/api/nuget/v3/index.json"
}
}
```

Caveats: `common-utils` runs `apt-get`, so your apt sources must be reachable.
Features that don't declare `installsAfter: common-utils` can still sort ahead
of this one. `defaultFeatures` only applies to the local VS Code Dev Containers
extension — not to Codespaces, the `devcontainer` CLI in CI, or other tools.

### Most robust: your base image

Configuration baked into your organization's base image exists before any
Feature runs.

If `npm`, `pip`, `uv`, `dotnet`, … already exist when this Feature runs, it prints
a warning: an earlier Feature (or the base image) installed them and may already
have fetched packages from public feeds.

## Environment-variable-only tools

**Corepack** (used via `corepack enable` and the `packageManager` field in
`package.json`) ignores `.npmrc` and can only be redirected with the
`COREPACK_NPM_REGISTRY` environment variable. Because `containerEnv` cannot
contain option values, the Feature writes such variables to
`/etc/package-source-overrides/env.sh`, which is sourced:

- **at runtime** by login shells via `/etc/profile.d/package-source-overrides.sh`
(this includes VS Code's environment probe, terminals, and lifecycle commands);
- **at build time**, only when `bashEnvHook` is `true`: `BASH_ENV` makes every
non-interactive bash process (such as the install scripts of Features that run
after this one) source it. `sh`/`dash` scripts don't honor `BASH_ENV`.

`BASH_ENV` is always set to `/etc/package-source-overrides/bash_env` (static
`containerEnv`); the file only exists when `bashEnvHook` is enabled or when the
base image already had a `BASH_ENV`, which is chained so it keeps working.

## Notes

- **No authentication** is configured — the override sources are expected to allow
Expand All @@ -73,6 +151,10 @@ it first in `overrideFeatureInstallOrder`:
- **Replace policy:** public feeds are replaced, not augmented. Only the override
sources are consulted.
- **Self-signed / HTTP proxies:** set `strictSsl` to `false` to emit
`strict-ssl=false` (npm), `trusted-host` (pip), and `allowInsecureConnections`
(NuGet). Leave it `true` whenever your proxy presents a trusted certificate.
- Leave an ecosystem's URL empty to skip configuring it.
`strict-ssl=false` (npm), `enableStrictSsl: false` (Yarn Berry), `trusted-host`
(pip), `allow-insecure-host` (uv), and `allowInsecureConnections` (NuGet). An
`http://` npm registry is also added to Yarn Berry's `unsafeHttpWhitelist`.
Leave `strictSsl` `true` whenever your proxy presents a trusted certificate.
- URL options must be `http(s)://` URLs without whitespace, quotes, backslashes,
backticks, `$`, `<`, or `>`.
- Leave a URL empty to skip configuring the tools that use it.
Loading
Loading