Skip to content

Commit eb2a2ed

Browse files
feat: improve domain scaffolding with container-first deploy, uniqueString, and lessons learned
- Add Lessons Learned section to agent with 8 codified patterns from Code Quality iteration - Container-first deployment: all 5 demo apps deploy as Docker containers via ACR + Web App for Containers - Global uniqueness: all Bicep templates use uniqueString(resourceGroup().id) for multi-student workshops - OIDC JSON fix: temp file pattern for federated credentials (PowerShell quote mangling) - Environment consistency: standardize on 'prod' (not 'production') across workflows and credentials - GitHub Pages fixes: remote_theme, correct baseurl, github-pages gem - Scanner repo secrets: deploy-all.yml needs OIDC secrets on scanner repo too - Codespace-first philosophy: all apps runnable locally via docker build/run - Per-domain OIDC app: avoid 20-credential limit by not sharing across domains - Updated Bicep template with ACR + Web App for Containers + uniqueString outputs - Updated deploy workflow template to use az acr build + container deploy pattern - Added Dockerfile requirements, port conventions, and Run Locally section specs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9d287cf commit eb2a2ed

3 files changed

Lines changed: 432 additions & 81 deletions

File tree

agents/domain-scaffolder.agent.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ Generate the complete `{domain}-scan-demo-app` repository structure following th
8181
8. Create `infra/storage.bicep` for ADLS Gen2 storage.
8282
9. Create `docs/` with overview, Power BI data model, and workshop setup documentation.
8383
10. Create `README.md` with project overview.
84-
11. Generate `.github/workflows/deploy.yml` for each demo app directory. Each deploy workflow MUST include: OIDC login, resource group creation via Bicep, language-specific build step, App Service deployment, health check, and `GITHUB_STEP_SUMMARY` output. Use the per-language deploy workflow template from the scaffolding skill.
85-
12. Include repository metadata in bootstrap scripts: set topics array, repository description, and enable GitHub Advanced Security code scanning for each demo app repo. Follow the repository metadata conventions from the scaffolding instructions.
84+
11. Generate `.github/workflows/deploy.yml` for each demo app directory. Each deploy workflow MUST use **containerized deployment**: Docker build → ACR push → Web App for Containers deploy. Include: OIDC login, resource group creation via Bicep, `az acr build`, deploy to Web App for Containers via `azure/webapps-deploy@v3` with `images:` parameter, health check, and `GITHUB_STEP_SUMMARY` output. Use the container deploy workflow template from the scaffolding skill.
85+
12. All `infra/main.bicep` files MUST use `uniqueString(resourceGroup().id)` for globally-scoped resource names (ACR, App Service, App Service Plan). Bicep MUST provision ACR + Web App for Containers (Linux, Docker), not Oryx code-deploy App Service.
86+
13. Include repository metadata in bootstrap scripts: set topics array, repository description, enable GitHub Advanced Security code scanning for each demo app repo, and **set OIDC secrets on the scanner repo** in addition to individual app repos. Follow the repository metadata conventions from the scaffolding instructions.
87+
14. All demo apps MUST be runnable locally in GitHub Codespaces via `docker build -t app . && docker run -p 3000:3000 app` without requiring Azure deployment.
8688

8789
### Step 5: Generate Workshop Repository
8890

@@ -110,6 +112,8 @@ Configure repository-level settings that must be applied after content is pushed
110112
3. Set **repository description** on both repos using `gh repo edit --description`.
111113
4. Set **repository topics** on both repos using `gh repo edit --add-topic` for each topic in the domain topics array.
112114
5. Set the **website URL** on the workshop repo to its GitHub Pages URL.
115+
6. Set **OIDC secrets** (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID`) on the **scanner repo** — the `deploy-all.yml` runs there.
116+
7. Create the **`prod` environment** on the scanner repo (not `production` — match the federated credential subject).
113117

114118
### Step 7: Produce Summary
115119

@@ -156,9 +160,73 @@ After scaffolding is complete:
156160
- Hand off to **CodeQualityDetector** to scan generated sample apps and verify they contain sufficient intentional violations (minimum 15 findings per app).
157161
- Hand off to **TestGenerator** to generate initial test suites for the sample apps to establish baseline coverage metrics.
158162

163+
## Lessons Learned from Prior Iterations
164+
165+
Apply these lessons when scaffolding any new domain. These were discovered during the Code Quality domain scaffolding and MUST be codified in all generated artifacts.
166+
167+
### 1. Container-First Deployment (Web App for Containers)
168+
169+
All 5 demo apps MUST deploy as **Docker containers** to Azure Web App for Containers. Do NOT use Oryx-based source deployment — it fails for Go and is fragile for other languages. The uniform container approach works for all languages and mirrors production patterns.
170+
171+
- Every demo app already has a `Dockerfile` — use it as the deployment artifact.
172+
- Use Azure Container Registry (ACR) to push images, then deploy via `azure/webapps-deploy@v3` with the `images` parameter.
173+
- The ACR name MUST use Bicep `uniqueString(resourceGroup().id)` for global uniqueness.
174+
175+
### 2. Global Uniqueness via Bicep `uniqueString()`
176+
177+
Many students run workshops simultaneously. All globally-scoped Azure resource names MUST include `uniqueString(resourceGroup().id)` to avoid collisions:
178+
179+
- **ACR**: `acr${uniqueString(resourceGroup().id)}` (3–50 chars, alphanumeric only)
180+
- **App Service**: `${appName}-${uniqueString(resourceGroup().id)}`
181+
- **App Service Plan**: `plan-${appName}-${uniqueString(resourceGroup().id)}`
182+
- **Storage Account**: `st${uniqueString(resourceGroup().id)}` (3–24 chars, alphanumeric only)
183+
184+
### 3. OIDC JSON Quoting in PowerShell
185+
186+
When passing JSON to Azure CLI from PowerShell, NEVER use inline `ConvertTo-Json -Compress`. PowerShell mangles the quotes. Always write to a temp file and pass `@$tempFile`:
187+
188+
```powershell
189+
$credParams = @{ name = $credName; issuer = $issuer; subject = $subject; audiences = @($audience) }
190+
$tempFile = [System.IO.Path]::GetTempFileName()
191+
$credParams | ConvertTo-Json | Set-Content -Path $tempFile -Encoding UTF8
192+
az ad app federated-credential create --id $appId --parameters "@$tempFile"
193+
Remove-Item -Path $tempFile -Force
194+
```
195+
196+
### 4. Consistent Environment Names
197+
198+
OIDC federated credentials and GitHub Actions workflows MUST use the same environment name. Standardize on `prod` (not `production`):
199+
200+
- Federated credential subject: `repo:{org}/{repo}:environment:prod`
201+
- Workflow: `environment: prod`
202+
- GitHub environment: `gh api repos/{org}/{repo}/environments/prod --method PUT`
203+
204+
### 5. Secrets on Scanner Repo
205+
206+
The `deploy-all.yml` workflow runs on the **scanner repo** (`{domain}-scan-demo-app`), not on individual demo app repos. The bootstrap script MUST also set `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_SUBSCRIPTION_ID` on the scanner repo itself, and create the `prod` environment there.
207+
208+
### 6. GitHub Pages Configuration
209+
210+
For project sites (non-org pages), workshop `_config.yml` MUST:
211+
212+
- Set `baseurl: "/{repo-name}"` (not empty string)
213+
- Use `remote_theme: just-the-docs/just-the-docs` (not `theme: just-the-docs`)
214+
- Use `github-pages` gem (not `jekyll` gem directly) in the `Gemfile`
215+
216+
### 7. Codespace-First Philosophy
217+
218+
All demo apps MUST be runnable and testable inside GitHub Codespaces without Azure deployment. Students can always `docker build` and `docker run` locally in a Codespace. Lab instructions SHOULD include a "Run Locally" alternative for each deploy step.
219+
220+
### 8. Per-Domain OIDC App Registration
221+
222+
Azure AD federated identity credentials have a **maximum of 20 per app**. Do NOT share a single OIDC app across multiple domains. Each domain MUST create its own dedicated app registration (e.g., `code-quality-scan-demo-app-oidc`).
223+
159224
## Conventions
160225

161226
Follow all conventions defined in `instructions/domain-scaffolding.instructions.md` for naming, SARIF standards, bootstrap scripts, CI/CD pipelines, Power BI PBIP, workshop labs, demo app violations, and screenshot automation.
162227

163228
- **PowerShell Only**: All generated commands in screenshot manifests, lab instructions, bootstrap scripts, and CI/CD pipelines MUST use PowerShell Core syntax. Never generate Unix-only commands (`head`, `tail`, `cat`, `2>/dev/null`, `/tmp/`, `./script`).
164229
- **Idempotent Bootstrap**: All bootstrap scripts MUST be safe to re-run without errors. Every resource creation step MUST check for existing resources before creating.
230+
- **Container-First**: All demo apps deploy as Docker containers via ACR + Web App for Containers. Never use Oryx source deployment.
231+
- **Global Uniqueness**: All Azure resource names with global scope MUST use `uniqueString(resourceGroup().id)` in Bicep.
232+
- **Codespace-Ready**: All demo apps MUST be buildable and runnable in Codespaces via `docker build && docker run`.

instructions/domain-scaffolding.instructions.md

Lines changed: 155 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,25 +215,25 @@ Each sample app directory (`{prefix}-demo-app-001` through `005`) is pushed to i
215215

216216
### Deploy Workflow Requirements
217217

218+
Every per-app deploy.yml MUST use **containerized deployment** (Web App for Containers). Do NOT use Oryx-based source deployment — it fails for Go and is fragile for other languages. The container approach works identically for all 5 languages and mirrors production deployment patterns.
219+
218220
Every per-app deploy.yml MUST include these stages:
219221

220222
1. **Azure Login** — OIDC federated credential login (`azure/login@v2`)
221223
2. **Resource Group** — Create or verify resource group via `az group create`
222-
3. **Infrastructure** — Deploy `infra/main.bicep` via `az deployment group create`
223-
4. **Build** — Language-specific build step (see table below)
224-
5. **Deploy** — Deploy to Azure App Service or Container App
224+
3. **Infrastructure** — Deploy `infra/main.bicep` via `az deployment group create` (provisions ACR + Web App for Containers)
225+
4. **Build & Push** — Build Docker image and push to ACR via `az acr build`
226+
5. **Deploy** — Deploy container to Web App for Containers via `azure/webapps-deploy@v3` with `images:` parameter
225227
6. **Health Check** — Verify the deployed app responds with HTTP 200
226228
7. **Summary** — Write deployed URL and status to `$GITHUB_STEP_SUMMARY`
227229

228230
### Language-Specific Build Steps
229231

230-
| Language | Build Command | Output |
231-
|----------|---------------|--------|
232-
| C# | `dotnet publish -c Release -o ./publish` | `./publish/` directory |
233-
| Python | `pip install -r requirements.txt` | In-place |
234-
| Java | `.\gradlew.bat build` (or `mvn package`) | `build/libs/*.jar` or `target/*.jar` |
235-
| TypeScript | `npm ci && npm run build` | `dist/` or `.next/` directory |
236-
| Go | `go build -o ./app .` | `./app` binary |
232+
Language-specific build steps are **no longer needed** in the deploy workflow because all apps are built via `docker build` using their `Dockerfile`. Each demo app MUST include a production-ready `Dockerfile`.
233+
234+
### Deploy Workflow Environment Name
235+
236+
All deploy workflows MUST use `environment: prod` (not `production`). This MUST match the OIDC federated credential subject: `repo:{org}/{repo}:environment:prod`. Mismatched environment names cause OIDC login failures.
237237

238238
### Workflow Permissions
239239

@@ -243,6 +243,151 @@ permissions:
243243
contents: read
244244
```
245245

246+
## Global Uniqueness via Bicep `uniqueString()`
247+
248+
Many workshop students deploy simultaneously to the same Azure subscription. All globally-scoped Azure resource names MUST include `uniqueString(resourceGroup().id)` in Bicep to avoid naming collisions.
249+
250+
### Resources Requiring Global Uniqueness
251+
252+
| Resource | Name Pattern | Bicep Expression |
253+
|----------|-------------|-----------------|
254+
| ACR | `acr{uniqueString}` | `'acr${uniqueString(resourceGroup().id)}'` |
255+
| App Service | `{appName}-{uniqueString}` | `'${appName}-${uniqueString(resourceGroup().id)}'` |
256+
| App Service Plan | `plan-{appName}-{uniqueString}` | `'plan-${appName}-${uniqueString(resourceGroup().id)}'` |
257+
| Storage Account | `st{uniqueString}` | `'st${uniqueString(resourceGroup().id)}'` |
258+
259+
### Bicep Template Structure
260+
261+
Every `infra/main.bicep` MUST:
262+
263+
1. Accept a `appName` parameter for logical identification
264+
2. Use `uniqueString(resourceGroup().id)` for all globally-scoped names
265+
3. Provision **ACR** + **App Service Plan** (Linux) + **Web App for Containers**
266+
4. Output the computed ACR name, app name, and default hostname for use by the deploy workflow
267+
5. Use `linuxFxVersion: 'DOCKER|${acrName}.azurecr.io/${appName}:latest'` for container configuration
268+
269+
### Deploy Workflow Deriving Names from Bicep Outputs
270+
271+
Deploy workflows MUST read ACR name and app name from Bicep deployment outputs rather than hardcoding them:
272+
273+
```yaml
274+
- name: Deploy Infrastructure
275+
id: infra
276+
run: |
277+
outputs=$(az deployment group create \
278+
--resource-group ${{ env.AZURE_RG }} \
279+
--template-file infra/main.bicep \
280+
--parameters appName=${{ env.APP_NAME }} \
281+
--query 'properties.outputs' -o json)
282+
echo "acrName=$(echo $outputs | jq -r '.acrName.value')" >> $GITHUB_OUTPUT
283+
echo "appServiceName=$(echo $outputs | jq -r '.appServiceName.value')" >> $GITHUB_OUTPUT
284+
```
285+
286+
## Containerized Deployment Conventions
287+
288+
### Dockerfile Requirements
289+
290+
Every demo app MUST include a `Dockerfile` that:
291+
292+
1. Uses a multi-stage build where appropriate (e.g., build stage + runtime stage)
293+
2. Exposes a PORT (default `3000` for Node, `5000` for Python, `8080` for Java/Go/.NET)
294+
3. Sets a `HEALTHCHECK` instruction or relies on the App Service health probe
295+
4. Is optimized for layer caching (dependencies before source code)
296+
297+
### ACR Build Pattern
298+
299+
Use `az acr build` instead of `docker build` + `docker push` — it avoids needing Docker-in-Docker in GitHub Actions runners:
300+
301+
```yaml
302+
- name: Build and Push to ACR
303+
run: |
304+
az acr build \
305+
--registry ${{ steps.infra.outputs.acrName }} \
306+
--image ${{ env.APP_NAME }}:${{ github.sha }} \
307+
--image ${{ env.APP_NAME }}:latest \
308+
.
309+
```
310+
311+
### Web App Container Deploy
312+
313+
```yaml
314+
- name: Deploy to Web App
315+
uses: azure/webapps-deploy@v3
316+
with:
317+
app-name: ${{ steps.infra.outputs.appServiceName }}
318+
images: ${{ steps.infra.outputs.acrName }}.azurecr.io/${{ env.APP_NAME }}:${{ github.sha }}
319+
```
320+
321+
## Codespace-First Philosophy
322+
323+
All demo apps MUST be runnable and testable inside GitHub Codespaces without Azure deployment. This provides a zero-cost, zero-config development experience for workshop participants.
324+
325+
### Requirements
326+
327+
1. Every demo app MUST include a `README.md` section titled "Run Locally" with:
328+
```bash
329+
docker build -t {prefix}-demo-app-NNN .
330+
docker run -p 3000:3000 {prefix}-demo-app-NNN
331+
```
332+
2. Workshop lab instructions SHOULD include a "Run Locally in Codespace" alternative for each deploy step
333+
3. The workshop `.devcontainer/devcontainer.json` MUST include Docker-in-Docker feature for local builds
334+
335+
## OIDC Script Conventions
336+
337+
### JSON Quoting Fix
338+
339+
When passing JSON to Azure CLI from PowerShell, NEVER use inline `ConvertTo-Json -Compress`. PowerShell mangles the quotes when interpolating. Always write to a temp file:
340+
341+
```powershell
342+
$credParams = @{
343+
name = $credName
344+
issuer = "https://token.actions.githubusercontent.com"
345+
subject = $subject
346+
audiences = @("api://AzureADTokenExchange")
347+
}
348+
$tempFile = [System.IO.Path]::GetTempFileName()
349+
$credParams | ConvertTo-Json | Set-Content -Path $tempFile -Encoding UTF8
350+
az ad app federated-credential create --id $appId --parameters "@$tempFile"
351+
Remove-Item -Path $tempFile -Force
352+
```
353+
354+
### Per-Domain OIDC App Registration
355+
356+
Azure AD federated identity credentials have a **maximum of 20 per app**. With 6+ repos × 3 subjects (main/dev/prod) = 18+, a shared OIDC app across domains will hit this limit. Each domain MUST create its own dedicated app registration.
357+
358+
### Scanner Repo Secrets
359+
360+
The `deploy-all.yml` workflow runs on the scanner repo (`{domain}-scan-demo-app`). The OIDC setup and bootstrap scripts MUST also:
361+
362+
1. Set `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID` secrets on the scanner repo
363+
2. Create the `prod` environment on the scanner repo
364+
3. Add federated credentials for `repo:{org}/{domain}-scan-demo-app:environment:prod`
365+
366+
## GitHub Pages Configuration (Workshop)
367+
368+
### Jekyll Config
369+
370+
Workshop `_config.yml` MUST use these settings for project sites:
371+
372+
```yaml
373+
remote_theme: just-the-docs/just-the-docs
374+
baseurl: "/{repo-name}" # MUST be /{repo-name} for project sites, NOT empty string
375+
url: "https://{org}.github.io"
376+
```
377+
378+
Do NOT use `theme: just-the-docs` — it only works locally. Use `remote_theme` for GitHub Pages compatibility.
379+
380+
### Gemfile
381+
382+
Workshop `Gemfile` MUST use the `github-pages` gem:
383+
384+
```ruby
385+
source "https://rubygems.org"
386+
gem "github-pages", group: :jekyll_plugins
387+
```
388+
389+
Do NOT use `gem "jekyll"` or `gem "just-the-docs"` directly — they may not be compatible with GitHub Pages.
390+
246391
## Repository Metadata
247392

248393
Both generated repositories MUST have metadata configured via bootstrap scripts or repository setup steps.

0 commit comments

Comments
 (0)