Secrets and Configuration
Your Task API needs an API key to call an external service. You hardcode it in your Docker image. Then you realize everyone who pulls your image can extract that key. You move it to an environment variable. Better, but now your deployment YAML has the key in plain text. Anyone with cluster access can read your manifests.
This isn't a hypothetical concern. In production systems, credential leaks cause real damage. API keys get revoked, databases get accessed by unauthorized parties, and teams spend days rotating compromised secrets across dozens of services.
Dapr's secrets building block solves this by providing a unified API to access secrets from multiple stores---Kubernetes secrets, HashiCorp Vault, Azure Key Vault, AWS Secrets Manager---without your application knowing which store is being used. This lesson shows you how to retrieve secrets securely in your Python code and reference them in component configurations.
Creating Kubernetes Secrets
Before Dapr can retrieve secrets, they must exist in Kubernetes. The kubectl create secret command creates secrets from literal values or files.
Create a Secret for API Credentials
kubectl create secret generic api-credentials \
--from-literal=api-key=my-secret-key-123 \
--from-literal=api-secret=my-secret-value-456
Output:
secret/api-credentials created
Verify the Secret Exists
kubectl get secret api-credentials -o yaml
Output:
apiVersion: v1
kind: Secret
metadata:
name: api-credentials
namespace: default
type: Opaque
data:
api-key: bXktc2VjcmV0LWtleS0xMjM=
api-secret: bXktc2VjcmV0LXZhbHVlLTQ1Ng==
Notice the values are base64-encoded (not encrypted). Kubernetes secrets provide access control, not encryption at rest by default.
Create a Redis Password Secret
For later use with component YAML:
kubectl create secret generic redis-password \
--from-literal=password=redis-secret-pass
Output:
secret/redis-password created
The Kubernetes Secrets Store Component
Dapr needs a secrets store component to know where to retrieve secrets from. The Kubernetes secrets store is built-in---no extra installation required.
Component YAML
# components/kubernetes-secrets.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: kubernetes-secrets
namespace: default
spec:
type: secretstores.kubernetes
version: v1
metadata: []
This component:
- type:
secretstores.kubernetestells Dapr to use the Kubernetes Secrets API - metadata: Empty because Dapr runs inside Kubernetes and has native access
- name: Your code references this name when calling
get_secret()
Deploy the Component
kubectl apply -f components/kubernetes-secrets.yaml
Output:
component.dapr.io/kubernetes-secrets created
Verify Component Registration
kubectl get components
Output:
NAME AGE
kubernetes-secrets 5s
statestore 1h
pubsub 1h
Retrieving Secrets with DaprClient
The Secrets API endpoint is /v1.0/secrets/{store}/{key}. With the Python SDK, you use DaprClient.get_secret().
Synchronous Pattern
from dapr.clients import DaprClient
with DaprClient() as client:
secret = client.get_secret(
store_name='kubernetes-secrets',
key='api-credentials'
)
api_key = secret.secret.get('api-key')
print(f"Got API key: {api_key[:4]}...")
Output:
Got API key: my-s...
Async Pattern with Context Manager
For FastAPI applications, use the async pattern:
from dapr.clients.aio import DaprClient
from fastapi import FastAPI
app = FastAPI()
@app.get("/config")
async def get_config():
async with DaprClient() as client:
secret = await client.get_secret(
store_name='kubernetes-secrets',
key='api-credentials'
)
api_key = secret.secret.get('api-key')
return {"api_key_prefix": api_key[:4] + "..."}
Output:
{"api_key_prefix": "my-s..."}
Getting Specific Keys from a Secret
Kubernetes secrets can contain multiple key-value pairs. The get_secret() response includes all keys:
async with DaprClient() as client:
secret = await client.get_secret(
store_name='kubernetes-secrets',
key='api-credentials'
)
# Access individual keys
api_key = secret.secret.get('api-key')
api_secret = secret.secret.get('api-secret')
print(f"Key: {api_key}")
print(f"Secret: {api_secret}")
Output:
Key: my-secret-key-123
Secret: my-secret-value-456
Getting Bulk Secrets
To retrieve all secrets from a store:
async with DaprClient() as client:
secrets = await client.get_bulk_secret(
store_name='kubernetes-secrets'
)
for secret_name, values in secrets.secrets.items():
print(f"{secret_name}: {list(values.keys())}")
Output:
api-credentials: ['api-key', 'api-secret']
redis-password: ['password']
Referencing Secrets in Component YAML
Hard-coding credentials in component YAML defeats the purpose of secrets. Dapr supports secretKeyRef to pull values from a secrets store.
The Pattern
Instead of:
metadata:
- name: redisPassword
value: "my-plain-text-password" # BAD: exposed in YAML
Use:
metadata:
- name: redisPassword
secretKeyRef:
name: redis-password # Kubernetes secret name
key: password # Key within the secret
Complete Example: Redis State Store with Secret Password
# components/statestore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis-master.default.svc.cluster.local:6379
- name: redisPassword
secretKeyRef:
name: redis-password
key: password
auth:
secretStore: kubernetes-secrets
The auth.secretStore field tells Dapr which secrets store to use for resolving secretKeyRef values. Without this, Dapr doesn't know where to look up the secret.
Kafka Pub/Sub with SASL Credentials
# components/kafka-pubsub.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: kafka-pubsub
namespace: default
spec:
type: pubsub.kafka
version: v1
metadata:
- name: brokers
value: kafka-cluster.kafka.svc.cluster.local:9092
- name: authType
value: password
- name: saslUsername
secretKeyRef:
name: kafka-credentials
key: username
- name: saslPassword
secretKeyRef:
name: kafka-credentials
key: password
auth:
secretStore: kubernetes-secrets
Configuration API Overview
Secrets are for sensitive data that rarely changes. Configuration is for dynamic settings that may change at runtime---feature flags, rate limits, threshold values.
When to Use Each API
| Scenario | Use | Why |
|---|---|---|
| Database password | Secrets | Sensitive, access-controlled |
| API key | Secrets | Sensitive, access-controlled |
| Feature flag | Configuration | Non-sensitive, may change frequently |
| Rate limit value | Configuration | Non-sensitive, may need runtime updates |
| Service URL | Configuration | Non-sensitive, environment-specific |
| Encryption key | Secrets | Highly sensitive |
Configuration API Endpoint
GET /v1.0/configuration/{store}?key=feature-x&key=max-retries
Python SDK Usage
async with DaprClient() as client:
config = await client.get_configuration(
store_name='configstore',
keys=['feature-flag-x', 'max-retry-count']
)
for item in config.items:
print(f"{item.key}: {item.value}")
Output:
feature-flag-x: enabled
max-retry-count: 3
Configuration vs Secrets: Key Differences
| Aspect | Secrets API | Configuration API |
|---|---|---|
| Purpose | Sensitive credentials | Dynamic settings |
| Encryption | At rest (depends on store) | Not required |
| Caching | Typically cached | Can subscribe to changes |
| Access pattern | Read on startup | Read + subscribe |
| Examples | Passwords, API keys, certificates | Feature flags, thresholds |
Practical Integration: Task API with Secrets
Let's integrate secrets into the Task API from previous lessons.
The Scenario
Your Task API needs:
- API key for an external notification service
- Redis password for state store (via secretKeyRef)
Step 1: Create the Secret
kubectl create secret generic notification-api \
--from-literal=api-key=notif-key-abc123
Step 2: Update Your FastAPI Application
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from dapr.clients.aio import DaprClient
from pydantic import BaseModel
import json
class NotificationConfig:
api_key: str | None = None
notification_config = NotificationConfig()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Load secrets on startup
async with DaprClient() as client:
secret = await client.get_secret(
store_name='kubernetes-secrets',
key='notification-api'
)
notification_config.api_key = secret.secret.get('api-key')
print(f"Loaded notification API key: {notification_config.api_key[:4]}...")
yield
app = FastAPI(lifespan=lifespan)
@app.post("/tasks/{task_id}/notify")
async def notify_task_owner(task_id: str):
if not notification_config.api_key:
raise HTTPException(status_code=500, detail="Notification service not configured")
# Use the API key to call external service
# In production: httpx.post(url, headers={"Authorization": f"Bearer {notification_config.api_key}"})
return {"status": "notification_sent", "task_id": task_id}
Output (on startup):
Loaded notification API key: noti...
Step 3: Update State Store Component with secretKeyRef
# components/statestore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis-master.default.svc.cluster.local:6379
- name: redisPassword
secretKeyRef:
name: redis-password
key: password
auth:
secretStore: kubernetes-secrets
Now your state store password comes from Kubernetes secrets, not plain text YAML.
Reflect on Your Skill
You built a dapr-deployment skill in Lesson 0. Test and improve it based on what you learned.
Test Your Skill
Using my dapr-deployment skill, show me how to retrieve API credentials from Kubernetes secrets using Dapr's async Python client.
Does your skill include the get_secret() pattern?
Identify Gaps
Ask yourself:
- Does my skill explain the Kubernetes secrets store component?
- Does it show how to use
secretKeyRefin component YAML? - Can it help me decide between Secrets API and Configuration API?
Improve Your Skill
If you found gaps:
My dapr-deployment skill is missing secrets management patterns.
Update it to include:
1. Kubernetes secrets store component (secretstores.kubernetes)
2. DaprClient.get_secret() async pattern
3. secretKeyRef for referencing secrets in component YAML
4. auth.secretStore field requirement
Try With AI
You now understand Dapr's secrets and configuration building blocks. Use AI to apply these patterns to your own systems.
Setup
Open your AI assistant with context about your Dapr project. These prompts help you implement secure credential management.
Prompt 1: Retrieve Secrets in Your Application
Configure my Task API to get an API key from Kubernetes secrets via Dapr using the async DaprClient.
Requirements:
- Secret name: external-service-credentials
- Key within secret: api-key
- Load on application startup using FastAPI lifespan
- Make available to route handlers without re-fetching
Show me the complete implementation with error handling.
What you're learning: The startup-load pattern ensures secrets are available throughout your application's lifetime without repeated API calls. The lifespan context manager is the modern FastAPI pattern for initialization.
Prompt 2: Reference Secrets in Component YAML
Show me how to reference secrets in a Dapr component YAML using secretKeyRef.
I have:
- Kubernetes secret: database-credentials
- Keys in secret: username, password
- Component: state.postgresql
Show me the complete component YAML with:
1. Host from plain value
2. Username and password from secretKeyRef
3. The auth.secretStore configuration
What you're learning: The secretKeyRef pattern keeps sensitive data out of your YAML files. The auth.secretStore field is required for Dapr to know which secrets store resolves the references.
Prompt 3: Secrets vs Configuration Decision
What's the difference between Dapr's Secrets API and Configuration API? When should I use each?
I have these values to manage:
- Database connection string with password
- Feature flag for new notification system
- Maximum retry count for failed API calls
- API key for payment processor
- Threshold for alert notifications
For each, tell me which API to use and why.
What you're learning: Secrets are for sensitive data with access control requirements. Configuration is for dynamic settings that may need runtime updates or subscriptions. Mixing them up creates security risks or unnecessary complexity.
Safety Note
Never log full secret values. The pattern api_key[:4] + "..." shown in this lesson reveals only enough to confirm the right secret was loaded. In production, consider not logging secret prefixes at all---an attacker could use timing attacks or log access to reconstruct secrets. Test secret retrieval in development, then remove debug logging before deployment.