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
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ dotnet run --project DevProxy -- --detach --as-system-proxy false --port 0 --api
- **CLI Commands:** `DevProxy/Commands/` — built with System.CommandLine, registered via `DevProxyCommand.cs`
- **Configuration:** `DevProxy/config/devproxyrc.json` and other JSON files in `config/`
- **macOS scripts:** `trust-cert.sh`, `remove-cert.sh`, `toggle-proxy.sh` in `DevProxy/`
- **Dev Container Feature:** `features/src/dev-proxy` (published as `ghcr.io/dotnet/dev-proxy/dev-proxy`), tests in `features/test/dev-proxy`. Run tests from `features/` with `npx @devcontainers/cli features test -f dev-proxy .`. Bump the version in `devcontainer-feature.json` to publish a new Feature version

## Resources

Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/devcontainer-feature-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Dev Container Feature - Release

# Publishes the Dev Container Feature in features/src to GHCR
# (ghcr.io/dotnet/dev-proxy/dev-proxy). To release a new version of the
# Feature, bump the version in devcontainer-feature.json. Versions that are
# already published are skipped.

on:
workflow_dispatch:
push:
branches: [ main ]
paths: [ 'features/src/**' ]

permissions:
contents: read

jobs:
publish:
if: github.repository == 'dotnet/dev-proxy' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Publish the Feature
uses: devcontainers/action@bef2cf2d1a019b00b45e184f83035e01bd1972f3 # v1.4.4
with:
publish-features: 'true'
base-path-to-features: ./features/src
disable-repo-tagging: 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61 changes: 61 additions & 0 deletions .github/workflows/devcontainer-feature-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Dev Container Feature - Test

on:
workflow_dispatch:
push:
branches: [ main ]
paths: [ 'features/**', '.github/workflows/devcontainer-feature-test.yml' ]
pull_request:
paths: [ 'features/**', '.github/workflows/devcontainer-feature-test.yml' ]

permissions:
contents: read

env:
DEVCONTAINER_CLI_VERSION: 0.89.0

jobs:
test-autogenerated:
name: Test on ${{ matrix.baseImage }} (${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
runner: [ ubuntu-latest, ubuntu-24.04-arm ]
baseImage:
- debian:latest
- ubuntu:latest
- mcr.microsoft.com/devcontainers/base:ubuntu
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install the Dev Container CLI
run: npm install -g @devcontainers/cli@${{ env.DEVCONTAINER_CLI_VERSION }}
- name: Test the Feature with default options, installed once and twice
working-directory: features
run: devcontainer features test --skip-scenarios -f dev-proxy -i ${{ matrix.baseImage }} .

test-scenarios:
name: Test scenarios (${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
runner: [ ubuntu-latest, ubuntu-24.04-arm ]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install the Dev Container CLI
run: npm install -g @devcontainers/cli@${{ env.DEVCONTAINER_CLI_VERSION }}
- name: Test the Feature scenarios
working-directory: features
run: devcontainer features test -f dev-proxy --skip-autogenerated --skip-duplicated .

validate:
name: Validate the Feature metadata
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Validate devcontainer-feature.json
uses: devcontainers/action@bef2cf2d1a019b00b45e184f83035e01bd1972f3 # v1.4.4
with:
validate-only: 'true'
base-path-to-features: ./features/src
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ brew install dev-proxy
bash -c "$(curl -sL https://aka.ms/devproxy/setup.sh)"
```

**Dev containers and GitHub Codespaces** (installs Dev Proxy and trusts its certificate in the container, see [the Feature's docs](./features/src/dev-proxy/README.md)):

```json
"features": {
"ghcr.io/dotnet/dev-proxy/dev-proxy:1": {}
}
```

Download the preset for the API your app calls, and start Dev Proxy with it:

```bash
Expand Down
83 changes: 83 additions & 0 deletions features/src/dev-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Dev Proxy (dev-proxy)

Installs [Dev Proxy](https://aka.ms/devproxy), an API simulator that helps you test how your app handles API failures, rate limits, latency, and more. Trusts a per-container Dev Proxy root certificate system-wide.

## Example Usage

```json
"features": {
"ghcr.io/dotnet/dev-proxy/dev-proxy:1": {}
}
```

## Options

| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Dev Proxy version to install: 'latest', 'beta', or a specific version like '2.2.0' or 'v2.2.0'. | string | latest |
| trustCertificate | After the container is created, generate a Dev Proxy root certificate unique to the container and trust it system-wide and for common runtimes. | boolean | true |

## Customizations

### VS Code Extensions

- `garrytrinder.dev-proxy-toolkit`

## What the Feature does

When the image is built, the Feature:

- Installs Dev Proxy in `/opt/devproxy` and adds the `devproxy` command to `/usr/local/bin`. For beta versions, `devproxy-beta` is available too.
- Installs the required dependencies (`curl`, `unzip`, `ca-certificates`, `openssl`, ICU) if they're missing.
- Creates the Dev Proxy configuration folder (`~/.config/dev-proxy`) for the remote user.
- If `trustCertificate` is `true`, installs a root-owned helper that adds the Dev Proxy root certificate to the system certificate store, and allows the remote user to run it with `sudo` without a password.

The Feature doesn't generate the Dev Proxy root certificate when building the image. If it did, the certificate and its private key would end up in an image layer, and everyone using the same image or Codespaces prebuild would share the same trusted CA.

Instead, after the container is created (`postCreateCommand`), the Feature:

1. Runs `devproxy cert ensure` as the remote user to generate a root certificate unique to the container.
2. Adds the certificate to the system certificate store (`/usr/local/share/ca-certificates` and `update-ca-certificates`), so that curl, .NET, and other tools that use the system store trust it.

Some runtimes don't use the system certificate store, so the Feature points them at the system CA bundle (`/etc/ssl/certs/ca-certificates.crt`):

| Variable | Used by |
|----------|---------|
| `NODE_EXTRA_CA_CERTS` | Node.js |
| `REQUESTS_CA_BUNDLE` | Python `requests` |
| `SSL_CERT_FILE` | Python and other OpenSSL-based tools |

Java keystores aren't supported.

Python 3.13 and newer enable strict certificate verification (`VERIFY_X509_STRICT`), which rejects the certificates that Dev Proxy v3 and earlier generate for intercepted hosts. Use Dev Proxy v4 or newer (`"version": "beta"` until v4 is released) to call APIs through Dev Proxy from Python 3.13+.

## Using Dev Proxy

The Feature doesn't start Dev Proxy or set `HTTP_PROXY`/`HTTPS_PROXY` for the whole container. Container-wide proxy variables would route package managers, git, and VS Code's own traffic through Dev Proxy, which breaks them when Dev Proxy isn't running.

Start Dev Proxy with your configuration, and configure the app you're testing to use it:

```bash
devproxy --config-file .devproxy/devproxyrc.json
```

```bash
HTTPS_PROXY=http://127.0.0.1:8000 npm start
```

## Not installing the Dev Proxy Toolkit

The Feature adds the [Dev Proxy Toolkit](https://marketplace.visualstudio.com/items?itemName=garrytrinder.dev-proxy-toolkit) VS Code extension. To skip it, exclude it in your `devcontainer.json`:

```json
"customizations": {
"vscode": {
"extensions": ["-garrytrinder.dev-proxy-toolkit"]
}
}
```

## OS support

The Feature supports Debian and Ubuntu-based images on x64 and arm64. Alpine and other musl-based images aren't supported.

48 changes: 48 additions & 0 deletions features/src/dev-proxy/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"id": "dev-proxy",
"version": "1.0.0",
"name": "Dev Proxy",
"documentationURL": "https://github.com/dotnet/dev-proxy/tree/main/features/src/dev-proxy",
"licenseURL": "https://github.com/dotnet/dev-proxy/blob/main/LICENSE",
"description": "Installs Dev Proxy, an API simulator that helps you test how your app handles API failures, rate limits, latency, and more. Trusts a per-container Dev Proxy root certificate system-wide.",
"keywords": [
"dev-proxy",
"devproxy",
"api",
"proxy",
"testing",
"mocking"
],
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"beta"
],
"default": "latest",
"description": "Dev Proxy version to install: 'latest', 'beta', or a specific version like '2.2.0' or 'v2.2.0'."
},
"trustCertificate": {
"type": "boolean",
"default": true,
"description": "After the container is created, generate a Dev Proxy root certificate unique to the container and trust it system-wide and for common runtimes."
}
},
"containerEnv": {
"NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt",
"REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt",
"SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt"
},
"customizations": {
"vscode": {
"extensions": [
"garrytrinder.dev-proxy-toolkit"
]
}
},
"postCreateCommand": "/usr/local/share/devproxy/post-create.sh",
"installsAfter": [
"ghcr.io/devcontainers/features/common-utils"
]
}
Loading
Loading