Skip to content

Modernize demo sample to Radius.* resource types and add Redis/PostgreSQL variants with secret binding - #2646

Open
willdavsmith wants to merge 3 commits into
edgefrom
willdavsmith-demo-sample-secret-pattern
Open

Modernize demo sample to Radius.* resource types and add Redis/PostgreSQL variants with secret binding#2646
willdavsmith wants to merge 3 commits into
edgefrom
willdavsmith-demo-sample-secret-pattern

Conversation

@willdavsmith

@willdavsmith willdavsmith commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Updates the demo sample to the current Radius.* resource types and adds two datastore variants that show how to connect the demo container to a managed cache and database. This mirrors #2645, and additionally applies the Radius secret-binding pattern so credentials are never rendered as plain container env values.

Changes

  • samples/demo/app.bicep

    • Migrate from deprecated Applications.* (2023-10-01-preview) to Radius.* (2025-08-01-preview) resource types.
    • Split into a dedicated Radius.Core/applications resource plus a Radius.Compute/containers resource using the new nested containers map schema.
    • Derive environmentName from the Environment ID and suffix resource names (demo-${environmentName}) so multiple environments (dev/test/prod) can coexist in the same resource group without name collisions.
  • samples/demo/app-redis.bicep (new)

    • Demo container connected to a Radius.Data/redisCaches cache via a connections.redis link.
    • REDIS_URL is bound from the cache's managed secret with secretKeyRef.
  • samples/demo/app-postgresql.bicep (new)

    • Demo container connected to a Radius.Data/postgreSqlDatabases database via a connections.postgresql link.
    • Admin password supplied through a @secure() parameter (never stored in the file), stored in a Radius.Security/secrets resource and bound into the container with secretKeyRef.

The image parameter

All three files declare a default-valued image parameter:

@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'

This restores the parameter that existed in app.bicep before #2645 removed it. It is required by CI: the demo matrix entry in .github/workflows/test.yaml runs on pull requests and deploys with -p image=sampleregistry:5000/samples/demo, and ARM rejects -p for a parameter the template does not declare. That override exists on purpose — the workflow builds the demo image from samples/demo/ and pushes it to the local sampleregistry:5000 registry so the test exercises the freshly-built image; dropping the override would silently test the published ghcr.io image instead and defeat the job. Because the parameter is default-valued, rad deploy samples/demo/app.bicep with no arguments behaves exactly as documented. No workflow changes are needed — --application in deployArgs is a rad deploy CLI scope flag, not a template parameter.

Secret handling

Both variants deliberately avoid passing credentials as plain container env values:

  • In the Radius Kubernetes containers recipe, a container env entry of the form { value: <string> } is rendered literally into the Kubernetes pod spec and stored unencrypted on the containers resource — the containers resource type schema has no x-radius-sensitive marker on env.value. A secret passed this way is visible to anyone with get pod/describe deployment in the namespace, and via rad resource show.
  • By contrast, Radius.Security/secrets marks data.*.value as x-radius-sensitive: true, so Radius encrypts it at rest and redacts it on reads. Binding via env.valueFrom.secretKeyRef keeps the value out of the pod spec and out of container state.
  • For redisCaches, the recipe's url secret is already materialized into a managed Radius.Security/secrets resource reachable at redis.properties.secrets.name, so it is bound directly with secretKeyRef.
  • For postgreSqlDatabases, the password is a user-supplied input (a @secure() param) and the type exposes no secrets property, so the app creates its own Radius.Security/secrets resource to hand the password to the container. It is bound as CONNECTION_POSTGRESQL_PASSWORD, matching the CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME> scheme the demo app reads (see samples/demo/src/db/repository.ts). The connection itself supplies CONNECTION_POSTGRESQL_HOST, _PORT, _USERNAME, and _DATABASE, but never _PASSWORD — that property is x-radius-sensitive, so it redacts to null on reads and is skipped by the containers recipe. This binding fills the one gap the connection leaves rather than colliding with it.
  • We use explicit env + secretKeyRef rather than a second connections entry pointing at the secrets resource: a connection to a Radius.Security/secrets source produces envFrom.secretRef, and Kubernetes does not expand envFrom-sourced variables in $(VAR) references inside container args.

Deploy

rad deploy samples/demo/app.bicep
rad deploy samples/demo/app-redis.bicep
rad deploy samples/demo/app-postgresql.bicep -p password=$(openssl rand -hex 16)

Validation

bicep build was run locally against br:biceptypes.azurecr.io/radius:latest:

  • app.bicep — builds clean
  • app-postgresql.bicep — builds clean
  • app-redis.bicep — fails locally with BCP053: The type "redisCachesProperties" does not contain property "secrets".

The app-redis.bicep failure is a stale published type index, not a defect in the sample. The redis.properties.secrets.name + key url expression matches the committed redisCaches type definition in radius-project/resource-types-contrib (Data/redisCaches/redisCaches.yaml defines properties.secrets with the reserved name sub-property and documents this exact secretKeyRef binding), and it is identical to the expression used by that repo's own test app at Data/redisCaches/test/app.bicep. The type simply has not been republished to biceptypes.azurecr.io/radius:latest yet; the file will build once the index is refreshed.

…eSQL variants with secret binding

Mirrors #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.
@willdavsmith
willdavsmith requested review from a team as code owners August 14, 2026 19:18
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant