Context Helpers
Context helpers are built-in functions available in all app configuration functions. They help you write clean, DRY configuration without hardcoding values.
Overview
All context helpers are automatically available in app configuration functions:
apps: {
api: {
env: ({ secret, appName, production }) => ({
// Use helpers here
NODE_ENV: production ? 'production' : 'development',
SERVICE_NAME: appName,
JWT_SECRET: secret('api-secrets', 'JWT_SECRET')
// ✅ In your app: config.url('postgres', 'service')
})
}
}Note: Use explicit namespace variables (like production, dev, etc.) instead of auto-detected flags.
Metadata
Information about the current context.
project: string
Current project name from config.
env: ({ project }) => ({
PROJECT_NAME: project // -> 'my-project'
})namespace: string
Current namespace name.
env: ({ namespace }) => ({
NAMESPACE: namespace // -> 'production' or 'dev'
})appName: string
Current application name being configured.
env: ({ appName }) => ({
SERVICE_NAME: appName, // -> 'api', 'frontend', etc.
INSTANCE_ID: `${appName}-${Date.now()}`
})cluster: ClusterMetadata
Current cluster information.
interface ClusterMetadata {
name: string // Cluster name
apiServer: string // API server URL
context: string // Kubectl context
}env: ({ cluster }) => ({
CLUSTER_NAME: cluster.name,
K8S_API: cluster.apiServer
})Service Discovery
⚠️ Important: Use runtime config in your application code, not ENV variables.
For service-to-service communication, use config.url() in your application:
// ✅ In your application code (e.g., backend/src/index.ts):
import config from './tsops.config'
// Short DNS (same namespace) - best for most cases
const API_URL = config.url('api', 'service') // http://api
const POSTGRES_URL = config.url('postgres', 'service') // http://postgres
// Full cluster DNS (cross-namespace safe)
const API_URL = config.url('api', 'cluster') // http://api.prod.svc.cluster.local
// Public ingress URL (external)
const PUBLIC_URL = config.url('api', 'ingress') // https://api.example.comBenefits:
- ✅ Type-safe (compile-time checks)
- ✅ Respects
TSOPS_NAMESPACEenvironment variable - ✅ Single source of truth
- ✅ No container restarts needed
- ✅ Zero-downtime deployments work correctly
Use ENV variables ONLY for:
- Secrets (passwords, API keys)
- External services (outside the cluster)
- Feature flags and configuration
- Build-time values
Generators
Functions that generate resource names and labels.
label(key, value?)
Generate resource label selector following best practices.
Signature
label(key: string, value?: string): stringExamples
env: ({ label }) => ({
// Auto-generated value (project-app)
APP_LABEL: label('name'),
// -> 'app.kubernetes.io/name=my-project-api'
// Custom value
COMPONENT_LABEL: label('component', 'database'),
// -> 'app.kubernetes.io/component=database'
TIER_LABEL: label('tier', 'backend')
// -> 'app.kubernetes.io/tier=backend'
})Pattern
app.kubernetes.io/{key}={value || project-appName}resource(kind, name)
Generate resource name following project conventions.
Signature
resource(kind: ResourceKind, name: string): string
type ResourceKind = 'secret' | 'configmap' | 'pvc' | 'sa' | 'serviceaccount'Examples
env: ({ resource }) => ({
SECRET_NAME: resource('secret', 'api-keys'),
// -> 'my-project-api-api-keys'
PVC_NAME: resource('pvc', 'data'),
// -> 'my-project-api-data'
SA_NAME: resource('sa', 'worker'),
// -> 'my-project-api-worker'
})Pattern
{project}-{appName}-{name}[-{kind}]Note: ServiceAccount (sa) doesn't include kind suffix.
Secrets & ConfigMaps
Functions for referencing secrets and config maps in environment variables.
secret(secretName)
Reference entire secret as envFrom (all keys become environment variables).
env: ({ secret }) => secret('api-secrets')
// Generates: envFrom: [{ secretRef: { name: 'api-secrets' } }]secret(secretName, key)
Reference specific key from secret as valueFrom.
env: ({ secret }) => ({
JWT_SECRET: secret('api-secrets', 'JWT_SECRET'),
API_KEY: secret('api-secrets', 'API_KEY')
})
// Generates:
// env:
// - name: JWT_SECRET
// valueFrom:
// secretKeyRef:
// name: api-secrets
// key: JWT_SECRETconfigMap(configMapName)
Reference entire configMap as envFrom.
env: ({ configMap }) => configMap('api-config')
// Generates: envFrom: [{ configMapRef: { name: 'api-config' } }]configMap(configMapName, key)
Reference specific key from configMap.
env: ({ configMap }) => ({
LOG_LEVEL: configMap('api-config', 'LOG_LEVEL'),
FEATURE_FLAGS: configMap('api-config', 'FEATURES')
})Type Safety
Secret and ConfigMap names are type-safe based on your declarations:
export default defineConfig({
secrets: {
'api-secrets': { /* ... */ },
'db-secrets': { /* ... */ }
},
apps: {
api: {
env: ({ secret }) => ({
// ✅ Type-safe: 'api-secrets' is recognized
JWT: secret('api-secrets', 'JWT_SECRET'),
// ❌ Type error: 'unknown-secret' doesn't exist
// KEY: secret('unknown-secret', 'KEY')
})
}
}
})Utilities
Helper functions for common tasks.
env(key, fallback?)
Get environment variable with optional fallback.
Signature
env<T extends string = string>(key: string, fallback?: T): TExamples
apps: {
api: {
env: ({ env, production }) => ({
// Required in production, fallback in dev
JWT_SECRET: env('JWT_SECRET', production ? undefined : 'dev-jwt-secret'),
// Always has fallback
DEBUG: env('DEBUG', 'false'),
// Optional
SENTRY_DSN: env('SENTRY_DSN')
})
}
}Pattern
Returns process.env[key] if set, otherwise returns fallback (or empty string if no fallback).
template(str, vars)
Simple template string helper for complex string generation.
Signature
template(str: string, vars: Record<string, string>): stringExamples
env: ({ template, env }) => ({
// Database URL (for external database with credentials)
DATABASE_URL: template('postgresql://{user}:{pass}@{host}:{port}/{db}', {
user: env('DB_USER', 'admin'),
pass: env('DB_PASSWORD'),
host: env('DB_HOST', 'external-db.example.com'),
port: '5432',
db: 'myapp'
}),
// -> 'postgresql://admin:secret@external-db.example.com:5432/myapp'
// API endpoint
API_ENDPOINT: template('https://{domain}/api/v{version}', {
domain: 'example.com',
version: '1'
}),
// -> 'https://example.com/api/v1'
})Pattern
Replaces {key} with corresponding value from vars object. Missing keys are replaced with empty string.
Complete Example
import { defineConfig } from 'tsops'
export default defineConfig({
project: 'my-app',
namespaces: {
prod: {
domain: 'example.com',
production: true,
replicas: 3,
dbHost: 'prod-db.internal'
},
dev: {
domain: 'dev.example.com',
production: false,
replicas: 1,
dbHost: 'dev-db.internal'
}
},
clusters: { /* ... */ },
images: { /* ... */ },
secrets: {
'app-secrets': ({ production }) => ({
JWT_SECRET: production
? process.env.JWT_SECRET ?? ''
: 'dev-secret',
API_KEY: process.env.API_KEY ?? ''
})
},
apps: {
api: {
ingress: ({ domain }) => ({ domain: `api.${domain}` }),
env: ({
// Metadata
project,
namespace,
appName,
cluster,
// Generators
label,
resource,
// Secrets & ConfigMaps
secret,
configMap,
// Utilities
env,
template,
// Namespace variables
production,
dbHost,
replicas,
domain
}) => ({
// Metadata
PROJECT: project,
NAMESPACE: namespace,
SERVICE_NAME: appName,
CLUSTER: cluster.name,
// Namespace variables
NODE_ENV: production ? 'production' : 'development',
DEBUG: production ? 'false' : 'true',
// ✅ In your app code, use runtime config:
// import config from './tsops.config'
// const REDIS_URL = config.url('redis', 'service')
// const POSTGRES_URL = config.url('postgres', 'service')
// Secrets
JWT_SECRET: secret('app-secrets', 'JWT_SECRET'),
// Template for external database URL
DATABASE_URL: template('postgresql://{user}@{host}:{port}/myapp', {
user: env('DB_USER', 'admin'),
host: dbHost, // external database host from namespace variables
port: '5432'
}),
// Namespace variables
WORKER_COUNT: String(replicas * 2),
// Labels & Resources
APP_LABEL: label('name'),
DATA_VOLUME: resource('pvc', 'data')
})
}
}
})Migration from v4
Removed Helpers
serviceName(app) - REMOVED
Too simple. Use template string directly:
// ❌ Old
serviceName: (app) => `${project}-${app}`
// ✅ New
`${project}-${app}`secretKey(name, key) - REMOVED
Merged into secret():
// ❌ Old
env: ({ secretKey }) => ({
JWT: secretKey('api-secrets', 'JWT_SECRET')
})
// ✅ New
env: ({ secret }) => ({
JWT: secret('api-secrets', 'JWT_SECRET')
})configMapKey(name, key) - REMOVED
Merged into configMap():
// ❌ Old
env: ({ configMapKey }) => ({
CONFIG: configMapKey('api-config', 'app.json')
})
// ✅ New
env: ({ configMap }) => ({
CONFIG: configMap('api-config', 'app.json')
})Simplified API
The secret/configMap functions now support both use cases with a cleaner API:
// Reference entire secret/configMap (envFrom)
env: ({ secret }) => secret('api-secrets')
// Reference specific key (valueFrom)
env: ({ secret }) => ({
JWT: secret('api-secrets', 'JWT_SECRET')
})