Secrets & ConfigMaps
Learn how to securely manage secrets and configuration in tsops.
Secret Management
tsops provides powerful secret management with automatic validation.
Tip: Root-level secret/configMap definitions run during Node execution, so read environment variables with
process.env. Theenv()helper is only available inside app-levelenvfunctions.
Basic Usage
Define secrets at the config root level (not within apps):
export default defineConfig({
project: 'my-app',
namespaces: {
dev: { production: false },
prod: { production: true }
},
// Secrets are defined at config root level
secrets: {
'api-secrets': ({ production }) => ({
JWT_SECRET: production
? process.env.JWT_SECRET ?? ''
: 'dev-jwt',
DB_PASSWORD: production
? process.env.DB_PASSWORD ?? ''
: 'dev-password',
API_KEY: production
? process.env.API_KEY ?? ''
: 'dev-key'
})
},
apps: {
api: {
// Reference secrets in env using secret() helper
env: ({ secret }) => ({
JWT_SECRET: secret('api-secrets', 'JWT_SECRET'),
DB_PASSWORD: secret('api-secrets', 'DB_PASSWORD')
})
}
}
})envFrom: Reference Entire Secret
Use secret() helper to inject all keys from a secret:
// Define secrets at config root level
secrets: {
'api-secrets': ({ production }) => ({
JWT_SECRET: production
? process.env.JWT_SECRET ?? ''
: 'dev-jwt',
DB_PASSWORD: production
? process.env.DB_PASSWORD ?? ''
: 'dev-password'
})
},
apps: {
api: {
// Reference entire secret as envFrom
env: ({ secret }) => secret('api-secrets') // ← envFrom: secretRef
}
}This generates:
envFrom:
- secretRef:
name: api-secretsvalueFrom: Reference Specific Keys
Mix static values with secret references:
apps: {
api: {
env: ({ secret }) => ({
PORT: '3000', // Static
JWT_SECRET: secret('api-secrets', 'JWT_SECRET'), // From secret
DB_PASSWORD: secret('api-secrets', 'DB_PASSWORD') // From secret
})
}
}This generates:
env:
- name: PORT
value: "3000"
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: api-secrets
key: JWT_SECRET
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: api-secrets
key: DB_PASSWORDSecret Validation
tsops automatically validates secrets before deployment.
What's Validated
✅ No undefined values
✅ No placeholder values (change-me, replace-me, todo, fixme)
✅ No references to missing environment variables
Example: Missing Values
secrets: {
'api-secrets': {
JWT_SECRET: process.env.PROD_JWT, // ← undefined!
DB_PASSWORD: 'change-me' // ← placeholder!
}
}tsops will show:
❌ Secret "api-secrets" for app "api" contains missing or placeholder values.
Missing/placeholder keys:
- JWT_SECRET = "undefined"
- DB_PASSWORD = "change-me"
Secret does not exist in cluster (namespace: "production").
Please provide actual values by:
1. Setting environment variables before deployment
2. Updating your tsops.config.ts with real values
3. Creating the secret manually in the cluster firstFallback to Cluster Secrets
If validation fails, tsops checks if the secret exists in the cluster:
kubectl get secret api-secrets -n productionIf found, tsops uses the existing secret. This allows:
- ✅ Manual secret creation
- ✅ Secret rotation without config changes
- ✅ Different secrets per environment
ConfigMaps
Similar to secrets, but for non-sensitive configuration.
Basic Usage
ConfigMaps are defined at config root level, similar to secrets:
export default defineConfig({
// ConfigMaps at root level
configMaps: {
'api-config': {
LOG_LEVEL: 'info',
MAX_CONNECTIONS: '100',
FEATURE_FLAGS: 'auth,payments,notifications'
}
},
apps: {
api: {
// Reference in env
env: ({ configMap }) => ({
LOG_LEVEL: configMap('api-config', 'LOG_LEVEL')
})
}
}
})envFrom: Reference Entire ConfigMap
// Define at root level
configMaps: {
'api-config': {
LOG_LEVEL: 'info',
FEATURE_FLAGS: 'auth,payments'
}
},
apps: {
api: {
// Reference entire configMap as envFrom
env: ({ configMap }) => configMap('api-config')
}
}valueFrom: Reference Specific Keys
apps: {
api: {
env: ({ configMap }) => ({
LOG_LEVEL: configMap('api-config', 'LOG_LEVEL'),
MAX_CONNECTIONS: configMap('api-config', 'MAX_CONNECTIONS')
})
}
}Best Practices
✅ Use environment variables for secrets
secrets: {
'api-secrets': ({ production }) => ({
JWT_SECRET: production
? process.env.PROD_JWT ?? ''
: 'dev-jwt'
})
}Then deploy with:
PROD_JWT=xxx PROD_DB_PWD=yyy pnpm tsops deploy --namespace prod✅ Different secrets per environment
secrets: {
'api-secrets': ({ production }) => ({
JWT_SECRET: production
? process.env.JWT_SECRET ?? ''
: 'dev-jwt-secret',
DB_PASSWORD: production
? process.env.DB_PASSWORD ?? ''
: 'dev-password'
})
}✅ Use template for external database URLs
secrets: {
'api-secrets': ({ template, env }) => ({
// For EXTERNAL databases with credentials:
DATABASE_URL: template('postgresql://{user}:{pwd}@{host}:{port}/{db}', {
user: 'myuser',
pwd: env('DB_PASSWORD', ''),
host: env('DB_HOST', 'external-db.example.com'), // External host
port: '5432',
db: 'myapp'
})
})
}
// ⚠️ For INTERNAL services, use runtime config in your app code:
// import config from './tsops.config'
// const POSTGRES_URL = config.url('postgres', 'service')❌ Don't hardcode secrets
secrets: {
'api-secrets': {
JWT_SECRET: 'hardcoded-secret' // ❌ Never do this!
}
}❌ Don't commit secrets to git
# .gitignore
.env
.env.local
.env.productionRuntime Access
Access resolved environment variables at runtime directly from your config object:
// server.ts
import config from './tsops.config'
// Set TSOPS_NAMESPACE to determine which namespace to use
process.env.TSOPS_NAMESPACE = 'prod'
// Get resolved environment for an app
const dbPassword = config.env('api', 'DB_PASSWORD')
console.log('JWT_SECRET:', config.env('api', 'JWT_SECRET'))
console.log('DB_PASSWORD:', dbPassword)
// Or get URLs directly
console.log('Internal endpoint:', config.url('api', 'cluster'))
console.log('External endpoint:', config.url('api', 'ingress'))This provides:
- ✅ Single source of truth
- ✅ Type-safe environment access
- ✅ Works in dev and production
- ✅ No duplication
- ✅ Built-in - no extra packages needed
Security Checklist
- [ ] All production secrets from environment variables
- [ ] No secrets in git
- [ ] .env files in .gitignore
- [ ] Different secrets per environment
- [ ] Secret rotation plan
- [ ] Access controls on secrets
- [ ] Audit logging enabled