3. Add a connection
The Radius Demo application runs on its own, but most applications depend on other resources such as databases and caches. In this step you will add a PostgreSQL database and an authored Secret to the application, then connect the container to both resources.
Add a PostgreSQL database and connections
The app-postgresql.bicep definition builds on app.bicep by adding a Radius.Data/postgreSqlDatabases resource, a Radius.Security/secrets resource, a @secure() password parameter, and two connections entries on the container. It also configures verified TLS using a mounted public CA. The highlighted lines show these additions:
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
}
}
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'
env: {
PGSSLMODE: {
value: 'verify-full'
}
NODE_EXTRA_CA_CERTS: {
value: '/etc/postgresql-ca/ca.crt'
}
}
volumeMounts: [
{
volumeName: 'postgresqlCa'
mountPath: '/etc/postgresql-ca'
}
]
ports: {
web: {
containerPort: 3000
}
}
}
}
volumes: {
postgresqlCa: {
secretName: 'postgresql-${environmentName}-ca'
}
}
connections: {
postgresql: {
source: postgresql.id
}
postgresqlCredentials: {
source: postgresqlCredentials.id
}
}
}
}
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 postgresqlCredentials 'Radius.Security/secrets@2025-08-01-preview' = {
name: 'postgresql-credentials-${environmentName}'
properties: {
environment: environment
application: demoApp.id
data: {
password: {
value: password
}
}
}
}
The postgresql resource is provisioned by the PostgreSQL Recipe in the default Recipe Pack. The password parameter is declared with @secure(), which keeps its value out of deployment logs and history and lets you supply the password at deploy time instead of hardcoding it. The application passes that value directly to the database’s password property. Because the property is x-radius-sensitive, Radius encrypts it and redacts it from reads.
The application also stores the same value under the password key of the authored postgresqlCredentials Secret instead of relying on database Recipe result.secrets for a developer-owned credential. The Secret has the same application and environment as the other resources, so it shares their lifecycle. Its resource name is deliberately different from the postgresql-${environmentName}-credentials Kubernetes Secret owned by the PostgreSQL Recipe.
The postgresql connection tells Radius that the container depends on the database. Radius provisions the database first, then injects its ordinary connection values as CONNECTION_POSTGRESQL_HOST, CONNECTION_POSTGRESQL_PORT, CONNECTION_POSTGRESQL_DATABASE, and CONNECTION_POSTGRESQL_USERNAME.
The postgresqlCredentials connection points directly to the authored Secret. With a compatible Kubernetes Container Recipe, Radius projects its password key through a Kubernetes secretKeyRef as CONNECTION_POSTGRESQLCREDENTIALS_PASSWORD. The secret value remains in the Secret and is not copied into Recipe output or plaintext container configuration.
Caution: The Radius Demo displays all
CONNECTION_*environment variables it receives, including this password, to illustrate connection injection. Do not display secret values in a production application.Compatibility: Automatic Secret connection projection requires a compatible Radius runtime and Kubernetes Container Recipe. In older or mixed-version environments, keep an explicit container
env.valueFrom.secretKeyRefbinding instead of changing a working model. Azure Container Instances behavior is unchanged.
Redeploy the application
Prepare PostgreSQL TLS
Before deploying this edge sample, use a PostgreSQL Kubernetes Recipe with TLS support. The default required policy rejects plaintext TCP connections; optional permits plaintext while keeping TLS available. When the operator TLS Secret is absent or empty, the Recipes generate a self-signed development certificate that clients do not trust by default. This tutorial supplies an operator-managed server certificate and public CA because the demo uses verify-full to verify server identity, even when tls is optional.
Use a certificate from your organization’s certificate authority (CA) outside isolated development. Its DNS subject alternative names must include postgresql-<environment>.<namespace>.svc.cluster.local. Supply the leaf-first server certificate chain as server.crt, its matching unencrypted private key as server.key, and the public root CA as ca.crt.
For an isolated development cluster, the following example creates a test CA and server certificate valid for seven days. Use Bash or Zsh (for example, in WSL on Windows), OpenSSL 1.1.1 or newer, and kubectl. Check openssl version; the LibreSSL bundled with some macOS versions is not the same tool.
Run these commands in a new, empty directory outside your application checkout. Set the namespace to the existing Environment’s actual Kubernetes namespace from rad env show. If you already have certificates, set only environment and namespace and skip the certificate-generation commands.
environment=default
namespace=default # Replace with the namespace shown by rad env show.
hostname="postgresql-$environment.$namespace.svc.cluster.local"
(
set -e
umask 077
openssl req -x509 -newkey rsa:2048 -nodes -days 7 -subj /CN=postgresql-test-ca \
-addext 'basicConstraints=critical,CA:TRUE' \
-addext 'keyUsage=critical,keyCertSign,cRLSign' \
-keyout ca.key -out ca.crt
openssl req -new -newkey rsa:2048 -nodes -subj "/CN=$hostname" \
-keyout server.key -out server.csr
printf 'basicConstraints=critical,CA:FALSE\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:%s\n' \
"$hostname" > server.ext
openssl x509 -req -days 7 -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -extfile server.ext -out server.crt
)
Continue only after certificate generation succeeds. In the same terminal and directory, create the two Secrets:
kubectl create secret tls "postgresql-$environment-tls" -n "$namespace" \
--cert=server.crt --key=server.key
kubectl create secret generic "postgresql-$environment-ca" -n "$namespace" \
--from-file=ca.crt=ca.crt
Only ca.crt goes into the client CA Secret. Keep ca.key local and protected; never upload it to Kubernetes. The server private key goes only into the server TLS Secret, never the client container. After creating the Secrets, retain any private keys needed for rotation or recovery in protected operator storage, or remove disposable development copies when no longer needed. See the PostgreSQL Recipe TLS guide for certificate issuance and rotation.
The sample mounts the public CA at /etc/postgresql-ca/ca.crt and sets PGSSLMODE=verify-full and NODE_EXTRA_CA_CERTS for the existing PostgreSQL client. This verifies server identity as well as encrypting traffic; CONNECTION_POSTGRESQL_TLS alone does not configure client trust. Older plaintext-only Recipes are incompatible with this sample configuration.
Protect existing data before redeployment or rotation. The default Recipes have no durable database volume, and a replacement Pod starts with an empty database. Server certificate rotation requires updating the TLS Secret and explicitly restarting the database Deployment. When changing the CA, distribute it to clients first and restart the demo after updating its CA Secret.
Return to your application directory in the same terminal and deploy the updated definition from its published URL. The --parameters argument sets the database password, and $(openssl rand -hex 16) generates a random value on each deploy. This works in Bash and Zsh; in PowerShell, generate the value separately and pass it in:
rad deploy https://edge.docs.radapp.io/samples/demo/app-postgresql.bicep --parameters password="$(openssl rand -hex 16)"
Radius creates the database and authored Secret, then updates the container with both connections. Wait for database readiness rather than relying solely on deployment success, then forward a local port to the container:
kubectl rollout status deployment/"postgresql-$environment" -n "$namespace" --timeout=180s
kubectl port-forward svc/demo-default-web 3000:3000 -n "$namespace"
Open http://localhost:3000. The Radius Connections section now lists the postgresql database connection and the postgresqlCredentials Secret connection.
Create a todo, refresh the list to confirm it appears, then delete it. Successful database operations verify that the demo can connect using its TLS configuration; visible connection entries and a ready database alone do not verify client trust.
View the connections in the Dashboard
Start port forwarding for the Dashboard:
kubectl port-forward svc/dashboard 7007:80 -n radius-system
Open http://localhost:7007, select the demo-default application, and use the application graph to inspect the container’s connections to the postgresql-default database and postgresql-credentials-default Secret.
To learn more about modeling dependencies between resources, see How to model application dependencies using connections.
Clean up
Delete the Radius Demo application. Because the database and authored Secret belong to this application, Radius deletes them with the container:
rad application delete demo-default
The operator-owned server TLS and public CA Secrets are not application resources. Remove them explicitly when no consumers need them. In a new terminal, set the same environment and namespace you used during setup:
environment=default
namespace=default # Replace with the namespace shown by rad env show.
kubectl delete secret "postgresql-$environment-tls" "postgresql-$environment-ca" -n "$namespace"
Optionally, uninstall Radius and remove all of its data:
rad uninstall kubernetes --purge
Next steps
You have installed Radius, deployed the Radius Demo application, and connected it to a PostgreSQL database and an authored Secret. Continue with the hands-on labs for deeper, real-world scenarios.
Next step: Explore the labsFeedback
Was this page helpful?
Glad to hear it! Please feel free to star our repo and join our Discord server to stay up to date with the project.
Sorry to hear that. If you would like to also contribute a suggestion visit and tell us how we can improve.