Complete guide to configuring production environment variables in ShipSafe, with security best practices and troubleshooting.
Overview
Environment variables store sensitive configuration like API keys, database credentials, and service account secrets. ShipSafe uses a secure environment variable system with validation and safe access patterns.
Why Environment Variables:
- ✅ Security - Secrets never exposed in code
- ✅ Flexibility - Different values for dev/staging/production
- ✅ Best Practice - Industry standard for configuration
- ✅ Validation - ShipSafe validates required variables at runtime
Concept: Environment Variables
How Environment Variables Work
Environment variables are key-value pairs set in the operating system or hosting platform:
# Example
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSy...
STRIPE_SECRET_KEY=sk_live_...
Access in Code:
// Client-side (NEXT_PUBLIC_ prefix required)
const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
// Server-side (no prefix needed)
const secretKey = process.env.STRIPE_SECRET_KEY;
Public vs Private Variables
Public Variables (NEXT_PUBLIC_*):
- Exposed to browser/client bundles
- Safe to include in JavaScript
- Examples: Firebase client config, Stripe public key
Private Variables (no prefix):
- Server-side only
- Never exposed to client
- Examples: Stripe secret key, Firebase private key
ShipSafe's Secure Access:
// src/lib/security/env.ts
export function getEnv(name: string, fallback?: string): string {
const value = process.env[name];
if (!value) {
if (fallback !== undefined) return fallback;
// Fail fast if required variable is missing
throw new Error(
`❌ Missing required environment variable: ${name}
This variable is required for ShipSafe to run securely.
Add it to your .env.local or environment config.
Tip: Check [Environment Variables Documentation](/docs/features/environment-variables) for setup instructions.
`
);
}
return value;
}
Required Variables
Firebase Client Configuration (Public)
These are safe to expose to the browser (prefixed with NEXT_PUBLIC_):
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSy...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
NEXT_PUBLIC_FIREBASE_APP_ID=1:123456789:web:abc123
How ShipSafe Uses Them:
// src/lib/firebase/client.ts
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
Where to Find:
- Go to Firebase Console
- Select your project
- Go to Project Settings → General
- Scroll to Your apps section
- Copy config values
Firebase Admin Configuration (Private)
These are server-side secrets and must never be exposed:
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...\n-----END PRIVATE KEY-----\n"
FIREBASE_DATABASE_URL=https://your-project.firebaseio.com
Important: FIREBASE_PRIVATE_KEY must include escaped newlines (\n).
How ShipSafe Uses Them:
// src/lib/firebase/init.ts
import { initializeApp, cert } from "firebase-admin/app";
import { getEnv } from "@/lib/security/env";
// ShipSafe uses getEnv() helper for secure access
const adminApp = initializeApp({
credential: cert({
projectId: getEnv("FIREBASE_PROJECT_ID"),
clientEmail: getEnv("FIREBASE_CLIENT_EMAIL"),
privateKey: getEnv("FIREBASE_PRIVATE_KEY").replace(/\\n/g, "\n"),
}),
databaseURL: process.env.FIREBASE_DATABASE_URL || undefined,
});
Where to Find:
- Go to Firebase Console
- Select your project
- Go to Project Settings → Service Accounts
- Click Generate New Private Key
- Download JSON file
- Extract values from JSON
Security Note:
- Never commit service account keys to Git
- Store securely in hosting platform
- Rotate keys regularly
Stripe Configuration
API Keys:
# Test Mode (Development)
STRIPE_SECRET_KEY=sk_test_51...
STRIPE_PUBLIC_KEY=pk_test_51...
# Live Mode (Production)
STRIPE_SECRET_KEY=sk_live_51...
STRIPE_PUBLIC_KEY=pk_live_51...
Webhook Secret:
# Local Development (from Stripe CLI)
STRIPE_WEBHOOK_SECRET=whsec_...
# Production (from Stripe Dashboard)
STRIPE_WEBHOOK_SECRET=whsec_...
Price IDs:
# Test Mode Price IDs
STRIPE_PRICE_STARTER=price_test_...
STRIPE_PRICE_PRO=price_test_...
# Production Price IDs
STRIPE_PRICE_STARTER=price_...
STRIPE_PRICE_PRO=price_...
How ShipSafe Uses Them:
// src/lib/stripe/client.ts
import Stripe from "stripe";
import { getEnv } from "@/lib/security/env";
export function getStripeClient(): Stripe {
const secretKey = getEnv("STRIPE_SECRET_KEY"); // Secure access with validation
// Validate key format
if (!secretKey.startsWith("sk_")) {
throw new Error("Invalid Stripe secret key format");
}
return new Stripe(secretKey, {
apiVersion: "2025-02-24.acacia", // Use latest stable version
typescript: true,
});
}
Where to Find:
- API Keys: Stripe Dashboard → Developers → API keys
- Webhook Secret: Stripe Dashboard → Developers → Webhooks → Click endpoint → Signing secret
- Price IDs: Stripe Dashboard → Products → Click product → Copy Price ID
Application Configuration
# Production Domain
APP_DOMAIN=yourdomain.com
# Environment
NODE_ENV=production
How ShipSafe Uses Them:
// config.ts
const config = {
domainName: process.env.APP_DOMAIN || "shipsafe.st",
// ...
};
// middleware.ts (HTTPS enforcement)
const isProd = process.env.NODE_ENV === "production";
Setting Variables: Hosting Platform
Vercel
Via Dashboard:
- Go to your project in Vercel Dashboard
- Navigate to Settings → Environment Variables
- Click Add New
- Enter variable name and value
- Select environment:
- Production - Live site
- Preview - Pull request previews
- Development - Development branches
- Click Save
Via Vercel CLI:
# Install Vercel CLI
npm i -g vercel
# Login
vercel login
# Add environment variable
vercel env add STRIPE_SECRET_KEY production
Other Platforms
Railway:
- Go to project dashboard
- Click Variables tab
- Add variables in format:
KEY=value
Render:
- Go to service dashboard
- Navigate to Environment tab
- Add variables
Netlify:
- Go to site dashboard
- Navigate to Site settings → Environment variables
- Add variables
Security Best Practices
1. Never Commit Secrets
✅ Do:
- Add
.env.localto.gitignore - Use
.env.examplefor documentation - Store secrets in hosting platform
❌ Don't:
- Commit
.env.localto Git - Share secrets in code
- Store secrets in public repositories
2. Use Different Keys for Environments
✅ Do:
# Development
STRIPE_SECRET_KEY=sk_test_...
# Production
STRIPE_SECRET_KEY=sk_live_...
❌ Don't:
# Never use production keys in development
STRIPE_SECRET_KEY=sk_live_... # ❌ In .env.local
3. Rotate Keys Regularly
- ✅ Rotate keys every 90 days
- ✅ Revoke old keys after rotation
- ✅ Update keys in all environments
- ✅ Test after rotation
4. Validate Required Variables
ShipSafe validates environment variables:
// src/lib/security/env.ts
export function getEnv(name: string, fallback?: string): string {
const value = process.env[name];
if (!value && fallback === undefined) {
throw new Error(
`❌ Missing required environment variable: ${name}`
);
}
return value || fallback;
}
Usage:
// Safe access with validation
const secretKey = getEnv("STRIPE_SECRET_KEY");
// ✅ Throws error if missing
Formatting Special Values
Firebase Private Key
The FIREBASE_PRIVATE_KEY must include escaped newlines:
# ❌ Wrong - Single line
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC... -----END PRIVATE KEY-----"
# ✅ Correct - Escaped newlines
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...\n-----END PRIVATE KEY-----\n"
Vercel: When adding in Vercel dashboard, paste the entire key including newlines - Vercel handles escaping automatically.
Manual Formatting:
# Extract from service account JSON
# Replace actual newlines with \n
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
Multi-line Values
For multi-line values, use quotes:
# ✅ Quoted value
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# ✅ Quoted value with spaces
APP_NAME="My Awesome App"
Environment Variable Validation
Boot-Time Validation
ShipSafe validates required variables when the app starts:
// src/lib/security/env.ts
export function validateRequiredEnv(names: string[]) {
for (const name of names) {
if (!process.env[name]) {
throw new Error(
`❌ Environment variable "${name}" is missing!
ShipSafe requires this variable for secure operation.
Check your .env.example file for setup instructions.
`
);
}
}
}
Usage:
// Validate critical variables on startup
validateRequiredEnv([
"STRIPE_SECRET_KEY",
"FIREBASE_PRIVATE_KEY",
"FIREBASE_CLIENT_EMAIL",
]);
Runtime Validation
Access variables safely:
// ✅ Safe: Use getEnv() helper
import { getEnv } from "@/lib/security/env";
const secretKey = getEnv("STRIPE_SECRET_KEY"); // Throws if missing
// ✅ Safe: Provide fallback
const apiKey = getEnv("API_KEY", "default-value");
// ❌ Unsafe: Direct access
const secretKey = process.env.STRIPE_SECRET_KEY; // Could be undefined
Complete Variable Checklist
Firebase Client (Public)
-
NEXT_PUBLIC_FIREBASE_API_KEY -
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN -
NEXT_PUBLIC_FIREBASE_PROJECT_ID -
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET -
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID -
NEXT_PUBLIC_FIREBASE_APP_ID
Firebase Admin (Private)
-
FIREBASE_PROJECT_ID -
FIREBASE_CLIENT_EMAIL -
FIREBASE_PRIVATE_KEY(with escaped newlines) -
FIREBASE_DATABASE_URL
Stripe
-
STRIPE_SECRET_KEY(test or live) -
STRIPE_PUBLIC_KEY(test or live) -
STRIPE_WEBHOOK_SECRET(per environment) -
STRIPE_PRICE_STARTER -
STRIPE_PRICE_PRO
Application
-
APP_DOMAIN -
NODE_ENV
Optional
-
SENTRY_DSN(for error tracking) - Other service keys
Troubleshooting
Variable Not Found
Problem: Missing required environment variable: STRIPE_SECRET_KEY
Solutions:
- Check variable name matches exactly (case-sensitive)
- Verify variable is set in hosting platform
- Redeploy after adding variables
- Check environment (Production vs Preview)
Private Key Format Error
Problem: Firebase Admin initialization fails
Solutions:
- Ensure
FIREBASE_PRIVATE_KEYincludes\nfor newlines - Check key is wrapped in quotes
- Verify full key is present (including BEGIN/END markers)
- Test locally with
.env.localfirst
Different Values in Preview vs Production
Problem: Preview deployments use wrong keys
Solutions:
- Set variables for each environment separately in Vercel
- Use test keys for Preview environment
- Use production keys only for Production environment
Variable Not Updating
Problem: Changes to variables not reflected
Solutions:
- Redeploy after changing variables
- Clear build cache
- Verify variable is saved in platform dashboard
- Check for typos in variable names
Production Checklist
Before deploying to production:
- All environment variables set in hosting platform
- Production keys used (not test keys)
-
NODE_ENV=productionset -
APP_DOMAINmatches your domain - Firebase authorized domains updated
- Stripe webhook URL updated
- Variables validated (no missing values)
- Private keys properly formatted
Learn More
- Environment Variables Features - Complete variable guide
- Firebase Setup - Firebase configuration
- Stripe Setup - Stripe configuration
- Vercel Deployment - Vercel-specific setup