# .env.development
NODE_ENV=development
DATABASE_URL=mongodb://localhost:27017/pixelcraft-dev
REDIS_URL=redis://localhost:6379
API_URL=http://localhost:3001
FRONTEND_URL=http://localhost:5173
JWT_SECRET=dev-secret-not-for-prod
SENTRY_DSN=
LOG_LEVEL=debug
# .env.staging
NODE_ENV=staging
DATABASE_URL=mongodb+srv://staging-cluster.mongodb.net/pixelcraft-staging
REDIS_URL=redis://staging-redis:6379
API_URL=https://api-staging.pixelcraft.dev
FRONTEND_URL=https://staging.pixelcraft.dev
JWT_SECRET=${{ secrets.STAGING_JWT }}
SENTRY_DSN=https://xxx@sentry.io/staging
LOG_LEVEL=info
# .env.production
NODE_ENV=production
DATABASE_URL=mongodb+srv://prod-cluster.mongodb.net/pixelcraft
REDIS_URL=redis://prod-redis:6379
API_URL=https://api.pixelcraft.dev
FRONTEND_URL=https://pixelcraft.dev
JWT_SECRET=${{ secrets.PROD_JWT }}
SENTRY_DSN=https://xxx@sentry.io/prod
LOG_LEVEL=warn
// lib/config.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum([
'development', 'staging',
'production'
]),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string(),
API_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
SENTRY_DSN: z.string().optional(),
LOG_LEVEL: z.enum([
'debug', 'info', 'warn', 'error'
]),
});
export const config =
envSchema.parse(process.env);
// App crashes on startup if config
// is invalid. Better than crashing
// 3 hours later on the first request
// that needs DATABASE_URL.
// scripts/seed.ts
import { faker } from '@faker-js/faker';
async function seed() {
console.log('🌱 Seeding database...');
const db = await connectDB();
// Clear existing data
await db.dropDatabase();
// Create test users
const users = Array.from(
{ length: 20 }, () => ({
name: faker.person.fullName(),
email: faker.internet.email(),
password: await hash('test123'),
createdAt: faker.date.past(),
}));
await db.collection('users')
.insertMany(users);
// Create test images
const images = Array.from(
{ length: 200 }, (_, i) => ({
title: faker.lorem.words(3),
userId: users[i % 20]._id,
url: faker.image.url(),
public: Math.random() > 0.3,
filters: { brightness: 50 },
createdAt: faker.date.recent(),
}));
await db.collection('images')
.insertMany(images);
console.log(
'✅ Seeded 20 users, 200 images');
}
// npm run seed → fresh test data
# Updated CI pipeline:
#
# Pull Request:
# → lint + test + build
# → deploy PREVIEW (unique URL)
# → comment PR with preview link
#
# Merge to main:
# → lint + test + build
# → deploy to STAGING
# → run E2E tests against staging
# → manual approval gate
# → deploy to PRODUCTION
#
# Each step has its own env vars.
# Staging JWT ≠ Production JWT.
# Staging DB ≠ Production DB.
# Complete isolation.
// Prevent dev from hitting prod DB:
// 1. Network isolation:
// Dev machine → can't reach prod DB
// (IP whitelist on Atlas)
// 2. Color-coded terminal prompts:
if (config.NODE_ENV === 'production') {
console.log(
'\x1b[41m 🔴 PRODUCTION \x1b[0m');
} else if (
config.NODE_ENV === 'staging') {
console.log(
'\x1b[43m 🟡 STAGING \x1b[0m');
} else {
console.log(
'\x1b[42m 🟢 DEVELOPMENT \x1b[0m');
}
// 3. Destructive action guards:
if (config.NODE_ENV === 'production') {
app.delete('/api/images/all',
(req, res) => {
res.status(403).json({
error: 'Bulk delete disabled' +
' in production',
});
});
}
git switch -c devops/PIXELCRAFT-093-environments
git add .env.* lib/config.ts scripts/seed.ts
git commit -m "Add dev/staging/prod environments (PIXELCRAFT-093)"
git push origin devops/PIXELCRAFT-093-environments
# PR → Preview deploy → Review → Merge ✅
Environment isolation is a principle of defense in depth.
Multiple barriers between a mistake and production damage. Each barrier catches different classes of errors.