From b7af63c223086cdf005bacfebbfdd222d826c0d7 Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Fri, 14 Aug 2026 12:17:57 -0700 Subject: [PATCH 1/3] Modernize demo sample to Radius.* resource types and add Redis/PostgreSQL variants with secret binding Mirrors radius-project/samples#2645 and additionally applies the Radius secret-binding pattern: secrets are bound into the demo container by reference with env valueFrom.secretKeyRef instead of plain env values. --- samples/demo/app-postgresql.bicep | 86 +++++++++++++++++++++++++++++++ samples/demo/app-redis.bicep | 60 +++++++++++++++++++++ samples/demo/app.bicep | 47 +++++++---------- 3 files changed, 166 insertions(+), 27 deletions(-) create mode 100644 samples/demo/app-postgresql.bicep create mode 100644 samples/demo/app-redis.bicep diff --git a/samples/demo/app-postgresql.bicep b/samples/demo/app-postgresql.bicep new file mode 100644 index 00000000..e6d3609f --- /dev/null +++ b/samples/demo/app-postgresql.bicep @@ -0,0 +1,86 @@ +extension radius + +@description('The Radius Environment ID. Injected automatically by the rad CLI.') +param environment string + +@description('The administrator password for the PostgreSQL database. Pass via the CLI, e.g. -p password=$(openssl rand -hex 16).') +@secure() +param password string + +// Environment name derived from the Environment ID, used to keep resource names +// unique per environment (e.g. dev/test/prod) within the same resource group. +var environmentName = last(split(environment, '/')) + +resource demoApp 'Radius.Core/applications@2025-08-01-preview' = { + name: 'demo-${environmentName}' + properties: { + environment: environment + } +} + +// The demo container needs the same password used to provision the database. +// Store it in a Radius.Security/secrets resource and bind it by reference rather +// than passing it to the container as a plain `env` value: `data.value` is +// x-radius-sensitive, so Radius encrypts it at rest and redacts it on reads, +// whereas a container `env.value` is stored unencrypted on the container +// resource and rendered literally into the pod spec. +resource dbCredentials 'Radius.Security/secrets@2025-08-01-preview' = { + name: 'postgresql-credentials-${environmentName}' + properties: { + environment: environment + application: demoApp.id + data: { + password: { + value: password + } + } + } +} + +resource postgresql 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' = { + name: 'postgresql-${environmentName}' + properties: { + environment: environment + application: demoApp.id + size: 'S' + database: 'appdb' + username: 'myadmin' + password: password + } +} + +resource demoContainer 'Radius.Compute/containers@2025-08-01-preview' = { + name: 'demo-${environmentName}' + properties: { + environment: environment + application: demoApp.id + containers: { + web: { + image: 'ghcr.io/radius-project/samples/demo:latest' + // Host, port, and database arrive automatically as CONNECTION_POSTGRESQL_* + // environment variables from the connection below. Only the password needs + // explicit wiring, and it is bound by reference via secretKeyRef. + env: { + POSTGRES_PASSWORD: { + valueFrom: { + secretKeyRef: { + secretName: dbCredentials.name + key: 'password' + } + } + } + } + ports: { + web: { + containerPort: 3000 + } + } + } + } + connections: { + postgresql: { + source: postgresql.id + } + } + } +} diff --git a/samples/demo/app-redis.bicep b/samples/demo/app-redis.bicep new file mode 100644 index 00000000..cfb19939 --- /dev/null +++ b/samples/demo/app-redis.bicep @@ -0,0 +1,60 @@ +extension radius + +@description('The Radius Environment ID. Injected automatically by the rad CLI.') +param environment string + +// Environment name derived from the Environment ID, used to keep resource names +// unique per environment (e.g. dev/test/prod) within the same resource group. +var environmentName = last(split(environment, '/')) + +resource demoApp 'Radius.Core/applications@2025-08-01-preview' = { + name: 'demo-${environmentName}' + properties: { + environment: environment + } +} + +resource redis 'Radius.Data/redisCaches@2025-08-01-preview' = { + name: 'redis-${environmentName}' + properties: { + environment: environment + application: demoApp.id + size: 'S' + } +} + +resource demoContainer 'Radius.Compute/containers@2025-08-01-preview' = { + name: 'demo-${environmentName}' + properties: { + environment: environment + application: demoApp.id + containers: { + web: { + image: 'ghcr.io/radius-project/samples/demo:latest' + // The recipe's `url` secret is NOT injected through the connection. It is + // materialized into a managed Radius.Security/secrets resource, so bind it + // by reference with secretKeyRef -- the value never lands in the pod spec. + env: { + REDIS_URL: { + valueFrom: { + secretKeyRef: { + secretName: redis.properties.secrets.name + key: 'url' + } + } + } + } + ports: { + web: { + containerPort: 3000 + } + } + } + } + connections: { + redis: { + source: redis.id + } + } + } +} diff --git a/samples/demo/app.bicep b/samples/demo/app.bicep index 4d31bd0b..6b034a11 100644 --- a/samples/demo/app.bicep +++ b/samples/demo/app.bicep @@ -1,40 +1,33 @@ extension radius -param application string +@description('The Radius Environment ID. Injected automatically by the rad CLI.') param environment string -param image string = 'ghcr.io/radius-project/samples/demo:latest' +// Environment name derived from the Environment ID, used to keep resource names +// unique per environment (e.g. dev/test/prod) within the same resource group. +var environmentName = last(split(environment, '/')) -resource demo 'Applications.Core/containers@2023-10-01-preview' = { - name: 'demo' +resource demoApp 'Radius.Core/applications@2025-08-01-preview' = { + name: 'demo-${environmentName}' properties: { - application: application - container: { - image: image - ports: { - web: { - containerPort: 3000 - } - } - livenessProbe: { - kind: 'httpGet' - containerPort: 3000 - path: '/healthz' - initialDelaySeconds: 10 - } - } - connections: { - redis: { - source: db.id - } - } + environment: environment } } -resource db 'Applications.Datastores/redisCaches@2023-10-01-preview' = { - name: 'db' +resource demoContainer 'Radius.Compute/containers@2025-08-01-preview' = { + name: 'demo-${environmentName}' properties: { - application: application environment: environment + application: demoApp.id + containers: { + web: { + image: 'ghcr.io/radius-project/samples/demo:latest' + ports: { + web: { + containerPort: 3000 + } + } + } + } } } From aac6655d559cf2fb95b46a65514a07ac0bcb883d Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Mon, 17 Aug 2026 09:30:08 -0700 Subject: [PATCH 2/3] Restore the image parameter on the demo sample bicep files PR #2645 dropped the 'image' parameter from samples/demo/app.bicep, but the demo entry in .github/workflows/test.yaml deploys with '-p image=sampleregistry:5000/samples/demo' so the test exercises the freshly-built image rather than the published ghcr.io one. ARM rejects -p for an undeclared parameter, so the job would fail. Restore the parameter (default-valued, so plain 'rad deploy' is unchanged) in all three files instead of editing the workflow. --- samples/demo/app-postgresql.bicep | 5 ++++- samples/demo/app-redis.bicep | 5 ++++- samples/demo/app.bicep | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/samples/demo/app-postgresql.bicep b/samples/demo/app-postgresql.bicep index e6d3609f..1156e3e4 100644 --- a/samples/demo/app-postgresql.bicep +++ b/samples/demo/app-postgresql.bicep @@ -3,6 +3,9 @@ extension radius @description('The Radius Environment ID. Injected automatically by the rad CLI.') param environment string +@description('Container image for the demo app. Defaults to the published sample image; overridden in CI to test a locally-built image.') +param image string = 'ghcr.io/radius-project/samples/demo:latest' + @description('The administrator password for the PostgreSQL database. Pass via the CLI, e.g. -p password=$(openssl rand -hex 16).') @secure() param password string @@ -56,7 +59,7 @@ resource demoContainer 'Radius.Compute/containers@2025-08-01-preview' = { application: demoApp.id containers: { web: { - image: 'ghcr.io/radius-project/samples/demo:latest' + image: image // Host, port, and database arrive automatically as CONNECTION_POSTGRESQL_* // environment variables from the connection below. Only the password needs // explicit wiring, and it is bound by reference via secretKeyRef. diff --git a/samples/demo/app-redis.bicep b/samples/demo/app-redis.bicep index cfb19939..d0d32741 100644 --- a/samples/demo/app-redis.bicep +++ b/samples/demo/app-redis.bicep @@ -3,6 +3,9 @@ extension radius @description('The Radius Environment ID. Injected automatically by the rad CLI.') param environment string +@description('Container image for the demo app. Defaults to the published sample image; overridden in CI to test a locally-built image.') +param image string = 'ghcr.io/radius-project/samples/demo:latest' + // Environment name derived from the Environment ID, used to keep resource names // unique per environment (e.g. dev/test/prod) within the same resource group. var environmentName = last(split(environment, '/')) @@ -30,7 +33,7 @@ resource demoContainer 'Radius.Compute/containers@2025-08-01-preview' = { application: demoApp.id containers: { web: { - image: 'ghcr.io/radius-project/samples/demo:latest' + image: image // The recipe's `url` secret is NOT injected through the connection. It is // materialized into a managed Radius.Security/secrets resource, so bind it // by reference with secretKeyRef -- the value never lands in the pod spec. diff --git a/samples/demo/app.bicep b/samples/demo/app.bicep index 6b034a11..0a3355a2 100644 --- a/samples/demo/app.bicep +++ b/samples/demo/app.bicep @@ -3,6 +3,9 @@ extension radius @description('The Radius Environment ID. Injected automatically by the rad CLI.') param environment string +@description('Container image for the demo app. Defaults to the published sample image; overridden in CI to test a locally-built image.') +param image string = 'ghcr.io/radius-project/samples/demo:latest' + // Environment name derived from the Environment ID, used to keep resource names // unique per environment (e.g. dev/test/prod) within the same resource group. var environmentName = last(split(environment, '/')) @@ -21,7 +24,7 @@ resource demoContainer 'Radius.Compute/containers@2025-08-01-preview' = { application: demoApp.id containers: { web: { - image: 'ghcr.io/radius-project/samples/demo:latest' + image: image ports: { web: { containerPort: 3000 From 6beb030bf8d5a82c91e6993be2084786afebe8c6 Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Mon, 17 Aug 2026 09:49:20 -0700 Subject: [PATCH 3/3] Bind the PostgreSQL password as CONNECTION_POSTGRESQL_PASSWORD The demo app reads its PostgreSQL credentials from CONNECTION_POSTGRESQL_* (see samples/demo/src/db/repository.ts), so the secretKeyRef binding must use CONNECTION_POSTGRESQL_PASSWORD. Bound as POSTGRES_PASSWORD the app fell through to an empty password and failed authentication. The connection supplies host, port, username, and database but never emits CONNECTION_POSTGRESQL_PASSWORD, because the property is x-radius-sensitive and redacts to null, so this binding fills that gap rather than colliding with it. --- samples/demo/app-postgresql.bicep | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/samples/demo/app-postgresql.bicep b/samples/demo/app-postgresql.bicep index 1156e3e4..24daa087 100644 --- a/samples/demo/app-postgresql.bicep +++ b/samples/demo/app-postgresql.bicep @@ -60,11 +60,14 @@ resource demoContainer 'Radius.Compute/containers@2025-08-01-preview' = { containers: { web: { image: image - // Host, port, and database arrive automatically as CONNECTION_POSTGRESQL_* - // environment variables from the connection below. Only the password needs - // explicit wiring, and it is bound by reference via secretKeyRef. + // Host, port, username, and database arrive automatically as + // CONNECTION_POSTGRESQL_* env vars from the connection below. The + // connection cannot carry the password (x-radius-sensitive properties + // redact to null on reads and are skipped by the containers recipe), so + // it is bound by reference here under the same naming scheme, filling + // the one gap the connection leaves. env: { - POSTGRES_PASSWORD: { + CONNECTION_POSTGRESQL_PASSWORD: { valueFrom: { secretKeyRef: { secretName: dbCredentials.name