Skip to main content
Version: v2

Inject Configuration from ConfigMaps and Secrets

This recipe walks through providing runtime configuration to a wasmCloud component from three sources: inline key-value pairs, Kubernetes ConfigMaps, and Kubernetes Secrets. All three are delivered to the component through the same interface, wasi:cli/environment, so the component reads them the same way regardless of source.

By the end, you will have a component that reads configuration values from all three sources, with a clear understanding of how precedence works when keys overlap.

Prerequisites

  • A Kubernetes cluster with wasmCloud installed via the Helm chart
  • kubectl
  • A component that reads wasi:cli/environment (see Configuration for a TypeScript walkthrough, or use the example below)

How it works

The localResources.environment field on a component accepts three sub-fields:

FieldSourcePrecedence
configInline key-value pairs in the manifestLowest
configFromReferences to Kubernetes ConfigMapsMiddle
secretFromReferences to Kubernetes SecretsHighest

All keys from all sources are merged into a single set of environment variables. When the same key appears in more than one source, the source with higher precedence wins. Within a single source (e.g. multiple ConfigMaps in configFrom), the last entry in the list wins.

Components can read the merged values by calling getEnvironment() (a part of the wasi:cli/environment interface), which returns all key-value pairs as an array.

Step 1: Create the ConfigMap

Create a ConfigMap with non-sensitive application configuration:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  APP_NAME: "My Service"
  LOG_LEVEL: "info"
  UPSTREAM_URL: "https://api.example.com"
shell
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  APP_NAME: "My Service"
  LOG_LEVEL: "info"
  UPSTREAM_URL: "https://api.example.com"
EOF

Step 2: Create the Secret

Create a Secret for sensitive values:

yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: default
type: Opaque
stringData:
  API_KEY: "sk-example-key-12345"
  DB_PASSWORD: "supersecret"
shell
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: default
type: Opaque
stringData:
  API_KEY: "sk-example-key-12345"
  DB_PASSWORD: "supersecret"
EOF

Secret values are delivered to the component as plain strings. No base64 decoding is required in your component code.

Step 3: Deploy the workload with all three sources

Create a WorkloadDeployment that combines inline values, the ConfigMap, and the Secret:

yaml
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadDeployment
metadata:
  name: configured-app
  namespace: default
spec:
  replicas: 1
  template:
    spec:
      hostSelector:
        hostgroup: default
      components:
        - name: http-component
          image: ghcr.io/wasmcloud/components/hello-world:0.1.0
          localResources:
            environment:
              config:
                REGION: "us-east-1"
                LOG_LEVEL: "debug"
              configFrom:
                - name: app-config
              secretFrom:
                - name: app-secrets
      hostInterfaces:
        - namespace: wasi
          package: http
          interfaces:
            - incoming-handler
          config:
            host: localhost
shell
kubectl apply -f - <<EOF
apiVersion: runtime.wasmcloud.dev/v1alpha1
kind: WorkloadDeployment
metadata:
  name: configured-app
  namespace: default
spec:
  replicas: 1
  template:
    spec:
      hostSelector:
        hostgroup: default
      components:
        - name: http-component
          image: ghcr.io/wasmcloud/components/hello-world:0.1.0
          localResources:
            environment:
              config:
                REGION: "us-east-1"
                LOG_LEVEL: "debug"
              configFrom:
                - name: app-config
              secretFrom:
                - name: app-secrets
      hostInterfaces:
        - namespace: wasi
          package: http
          interfaces:
            - incoming-handler
          config:
            host: localhost
EOF

Step 4: Understand precedence

In the manifest above, LOG_LEVEL appears in two sources:

  • config (inline) sets it to "debug"
  • configFrom references app-config, which sets it to "info"

Because configFrom has higher precedence than config, the component receives LOG_LEVEL=info.

The full set of values the component receives:

KeyValueSourceWhy
REGIONus-east-1configOnly appears in inline config
LOG_LEVELinfoconfigFromConfigMap overrides inline
APP_NAMEMy ServiceconfigFromOnly appears in ConfigMap
UPSTREAM_URLhttps://api.example.comconfigFromOnly appears in ConfigMap
API_KEYsk-example-key-12345secretFromOnly appears in Secret
DB_PASSWORDsupersecretsecretFromOnly appears in Secret

If app-secrets also contained a LOG_LEVEL key, the Secret value would win over both the inline and ConfigMap values.

Reading configuration in component code

Rust

Use the wasi:cli/environment bindings to read environment variables:

rust
fn get_config() -> std::collections::HashMap<String, String> {
    wasi::cli::environment::get_environment()
        .into_iter()
        .collect()
}

TypeScript

Import getEnvironment from wasi:cli/environment:

typescript
import { getEnvironment } from 'wasi:cli/environment@0.2.3';

const config = Object.fromEntries(getEnvironment());
const region = config['REGION'] ?? 'us-west-2';
const apiKey = config['API_KEY'];

Call getEnvironment() inside route handlers rather than at module scope. See the Configuration guide for a full TypeScript walkthrough including Hono integration.

Multiple ConfigMaps and Secrets

You can reference multiple ConfigMaps and multiple Secrets. Within each list, the last entry wins on key conflicts:

yaml
localResources:
  environment:
    configFrom:
      - name: base-config       # loaded first
      - name: override-config   # wins on conflicts
    secretFrom:
      - name: shared-secrets    # loaded first
      - name: app-secrets       # wins on conflicts

This is useful for layering a shared base configuration with per-application overrides.

Development workflow

During local development with wash dev, localResources.environment defaults to empty. Provide fallback defaults in your component code so the component runs correctly without configuration:

typescript
const config = Object.fromEntries(getEnvironment());
const region = config['REGION'] ?? 'us-west-2';
const logLevel = config['LOG_LEVEL'] ?? 'debug';

Test configuration-dependent behavior by deploying to a Kubernetes environment with the full manifest.

Clean up

shell
kubectl delete workloaddeployment configured-app
kubectl delete configmap app-config
kubectl delete secret app-secrets