This template uses Infisical for centralized secrets management with two approaches:
- Infisical CLI Auto-Sync (Recommended) - Automatically syncs secrets to local
.envfiles - SDK Integration - Direct API access with hierarchical resolution and fallback
# macOS
brew install infisical/get-cli/infisical
# Linux/WSL
curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.release.sh' | sudo -E bash
# Windows
scoop install infisical# Login to Infisical
infisical login
# Initialize project (creates .infisical.json)
infisical init# Auto-sync every 60 seconds to .env file
infisical run --daemon --env=dev --path=/ --interval=60
# Or add to package.json for convenience
yarn secrets:sync # Starts the daemonThe daemon will:
- Automatically sync secrets to
.envfile - Update when secrets change in Infisical
- Handle authentication refresh
- Work offline after initial sync
# Your app just reads from process.env - no SDK needed!
yarn dev # Reads from .env automaticallyIf you need programmatic access or dynamic updates:
# Best practice: Add to ~/.zshrc or ~/.bashrc for all projects
export INFISICAL_CLIENT_ID="your-machine-identity-client-id"
export INFISICAL_CLIENT_SECRET="your-machine-identity-client-secret"
export INFISICAL_APPS_PROJECT_ID="10b63b16-515c-4ea4-adfc-1da96d654fe8"
# Then reload shell
source ~/.zshrc # or ~/.bashrc
# No need for .env.local with credentials!import { secretManager } from '@adaptiveworx/shared/config';
// Application secrets (from worx-apps or app-specific project)
const apiKey = await secretManager.getRequiredSecret('API_KEY');
// Platform/infrastructure secrets (from worx-platform)
const awsKey = await secretManager.getPlatformSecret('AWS_ACCESS_KEY_ID');Pros:
- ✅ Simple - just read from
process.env - ✅ Works offline after sync
- ✅ No custom code needed
- ✅ Automatic updates via daemon
- ✅ Standard
.envworkflow
Cons:
- ❌ Requires CLI installation
- ❌ Secrets stored on disk (in
.env) - ❌ No programmatic CRUD operations
Best for: Local development, simple deployments, standard applications
Pros:
- ✅ Dynamic updates without restart
- ✅ No secrets on disk
- ✅ Programmatic CRUD operations
- ✅ Hierarchical project resolution
- ✅ Works in serverless/containers
Cons:
- ❌ More complex setup
- ❌ Network dependency
- ❌ Custom caching logic
Best for: Production, microservices, dynamic environments
graph LR
A[Infisical Cloud] -->|Sync| B[CLI Daemon]
B -->|Write| C[.env file]
C -->|Read| D[process.env]
D -->|Access| E[Your App]
B -->|Auto-refresh<br/>every 60s| B
graph TD
A[Your App] -->|Request| B[SecretManager]
B -->|Check| C{Cached?}
C -->|Yes| D[App Cache]
C -->|No| E[InfisicalSDK]
E -->|API Call| F[Infisical Cloud]
F -->|Hierarchical<br/>Resolution| G[app-specific → worx-apps]
G -->|Not Found| H[process.env]
D -->|Return| A
G -->|Return| A
H -->|Return| A
Secrets are resolved with this priority (most specific wins):
- App-specific (
INFISICAL_APP_PROJECT_ID→worx-{appname}) - Optional, for app-specific secrets - Shared apps (
INFISICAL_APPS_PROJECT_ID→worx-apps) - Default for all application secrets - Environment variables (fallback) - Local
.envfiles
Note: worx-platform (INFISICAL_PLATFORM_PROJECT_ID) contains infrastructure/platform secrets (AWS keys, monitoring, etc.) and is managed separately, not part of the application hierarchy.
Example:
// Default behavior (most common):
// Tries: worx-apps → process.env
const apiKey = await secretManager.getSecret('API_KEY');
// With app-specific project:
// Set INFISICAL_APP_PROJECT_ID=<your-app-project-id>
// Tries: worx-myapp → worx-apps → process.env
const appSpecificKey = await secretManager.getSecret('CUSTOM_API_KEY');import { SecretManager, getSecretManager } from './config/secrets';
// Create instance
const sm = new SecretManager({
appName: 'myapp', // Optional: enables app-specific project
environment: 'dev' // Optional: defaults to NODE_ENV or 'dev'
});
// Get secret (throws if not found)
const value = await sm.getSecret('API_KEY');
// Get with default
const port = await sm.getSecretOrDefault('PORT', '3000');
// Get multiple
const secrets = await sm.getSecrets(['DB_URL', 'API_KEY']);
// Health check
const health = await sm.healthCheck();
console.log(health.status); // 'healthy' | 'degraded' | 'unhealthy'
// Singleton pattern
const sm2 = getSecretManager();# Load secrets to .env.local
yarn infisical:load # dev environment
yarn infisical:load:dev # explicit dev
yarn infisical:load:stg # staging
yarn infisical:load:prd # production
# Validate connectivity
yarn infisical:validate
# Show secrets (don't write)
yarn infisical:show
# Custom output file
yarn infisical:load --output=.env.custom# Check Infisical connectivity
yarn config:health
# Output:
# {
# "status": "healthy",
# "message": "All Infisical projects accessible",
# "projects": [
# { "name": "worx-apps", "accessible": true },
# { "name": "worx-platform", "accessible": true }
# ]
# }These are automatically loaded from Infisical:
worx-apps project (baseline):
TYPE_COVERAGE_MIN=98.5
LINT_MAX_WARNINGS=0
TEST_COVERAGE_MIN=80Loaded via:
yarn infisical:load
source .env.local
yarn quality:validate # Uses loaded thresholdsFor app-specific secrets (API keys, DB credentials):
- Create app-specific Infisical project:
worx-myapp - Set
INFISICAL_APP_PROJECT_IDin.env.local - Add secrets to
worx-myapp/{env}/in Infisical
Precedence:
worx-myapp/dev/API_KEY # Highest priority
↓ (if not found)
worx-apps/dev/API_KEY # Shared apps
↓ (if not found)
worx-platform/dev/API_KEY # Platform fallback
↓ (if not found)
process.env.API_KEY # Environment variable
Option 1: Environment variables only
# .env.local (no Infisical credentials)
TYPE_COVERAGE_MIN=98.5
LINT_MAX_WARNINGS=0
TEST_COVERAGE_MIN=80
API_KEY=local-dev-key
# Load into shell
source .env.localOption 2: Use package.json defaults
# No .env.local needed
# package.json has default thresholds
yarn build
yarn testGitHub Actions: (already configured in .github/workflows/ci.yml)
env:
INFISICAL_PLATFORM_PROJECT_ID: ${{ secrets.INFISICAL_PLATFORM_PROJECT_ID }}
INFISICAL_APPS_PROJECT_ID: ${{ secrets.INFISICAL_APPS_PROJECT_ID }}
INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_CLIENT_ID }}
INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }}Required GitHub Secrets:
INFISICAL_PLATFORM_PROJECT_IDINFISICAL_APPS_PROJECT_IDINFISICAL_CLIENT_IDINFISICAL_CLIENT_SECRET
Cause: Environment variables not set
Fix:
source .env.local
yarn infisical:validateCause: Secret doesn't exist in any Infisical project
Fix:
- Add secret to Infisical (worx-apps or worx-platform)
- Or add to
.env.localas fallback
Cause: Invalid credentials or network issue
Fix:
# Verify credentials
echo $INFISICAL_CLIENT_ID
echo $INFISICAL_CLIENT_SECRET
# Test connection
yarn infisical:validateCause: Thresholds not loaded from Infisical
Fix:
# Load thresholds
yarn infisical:load
source .env.local
# Validate
yarn quality:validate- Never commit
.env.localor.env.*files (gitignored) - Rotate credentials regularly in Infisical
- Use app-specific projects for sensitive secrets
- Audit access via Infisical dashboard
- Least privilege - only grant necessary permissions
New developer onboarding:
# 1. Clone repo
git clone https://github.com/adaptiveworx/my-project
cd my-project
# 2. Setup Infisical
cp .env.example .env.local
# Add INFISICAL_CLIENT_ID and INFISICAL_CLIENT_SECRET
# 3. Load secrets
source .env.local
yarn infisical:validate # Verify connectivity
yarn infisical:load # Bootstrap .env.local
# 4. Develop
yarn install
yarn devSwitching environments:
# Load staging secrets
yarn infisical:load:stg
source .env.local
# Verify
yarn config:health
# Shows: "environment": "stg"
# Run tests against staging
yarn test