6. Переменные окружения

Environment variables — способ конфигурировать приложение без хардкода секретов в коде. Разные значения для dev, staging, production — без изменения кода.
Зачем это нужно
Заголовок раздела «Зачем это нужно»❌ Плохо (хардкод):const db = new Pool({ connectionString: 'postgresql://admin:[email protected]:5432/mydb' });
✅ Хорошо (env vars):const db = new Pool({ connectionString: process.env.DATABASE_URL });Преимущества:
- Секреты не попадают в git
- Легко менять конфигурацию без деплоя кода
- Один код — разное поведение на разных окружениях
.env файлы
Заголовок раздела «.env файлы»# .env.local (не коммитить!)DATABASE_URL=postgresql://localhost:5432/mydb_devREDIS_URL=redis://localhost:6379JWT_SECRET=dev-secret-key-change-in-prodSMTP_HOST=localhostSMTP_PORT=1025
# .env.example (коммитить — документация)DATABASE_URL=postgresql://localhost:5432/mydbREDIS_URL=redis://localhost:6379JWT_SECRET=your-secret-key-hereSMTP_HOST=smtp.example.comSMTP_PORT=587.gitignore для secrets
Заголовок раздела «.gitignore для secrets».env.env.local.env.*.local.env.development.local.env.test.local.env.production.local
# Разрешаем коммитить!.env.example!.env.testNode.js: работа с env vars
Заголовок раздела «Node.js: работа с env vars»// Базовое использованиеconst port = process.env.PORT || 3000;const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) { throw new Error('DATABASE_URL is required');}
// Типизация и валидация с zodimport { z } from 'zod';
const envSchema = z.object({ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), SMTP_HOST: z.string(), SMTP_PORT: z.coerce.number().default(587), SMTP_USER: z.string().optional(), SMTP_PASS: z.string().optional(),});
export const env = envSchema.parse(process.env);
// Теперь используемimport { env } from './env.js';const server = express().listen(env.PORT);Next.js: переменные окружения
Заголовок раздела «Next.js: переменные окружения»# Серверные (не доступны в браузере)DATABASE_URL=postgresql://localhost:5432/mydbJWT_SECRET=supersecret
# Публичные (доступны в браузере через process.env)NEXT_PUBLIC_API_URL=https://api.myapp.comNEXT_PUBLIC_ANALYTICS_ID=UA-XXXXX// В server компонентах/API routesconst db = createClient({ url: process.env.DATABASE_URL }); // ✅
// В client компонентахconst apiUrl = process.env.NEXT_PUBLIC_API_URL; // ✅const secret = process.env.JWT_SECRET; // ❌ undefined в браузере!Иерархия .env файлов в Next.js
Заголовок раздела «Иерархия .env файлов в Next.js».env — базовые значения (коммитить можно).env.local — локальные (не коммитить).env.development — для npm run dev.env.production — для npm run build/start.env.test — для тестов.env.development.local — локальное переопределение dev.env.production.local — локальное переопределение prodПриоритет: .env.*.local > .env.* > .env.local > .env
dotenv для Node.js
Заголовок раздела «dotenv для Node.js»// Ручная загрузка .envimport 'dotenv/config';
// Или с опциямиimport dotenv from 'dotenv';dotenv.config({ path: '.env.local' });dotenv.config({ path: '.env' }); // fallback
// Проверка обязательных переменныхconst required = ['DATABASE_URL', 'JWT_SECRET', 'SMTP_HOST'];const missing = required.filter(key => !process.env[key]);if (missing.length) { console.error('Missing env vars:', missing.join(', ')); process.exit(1);}Переменные для разных окружений
Заголовок раздела «Переменные для разных окружений»# .env (база — коммитить)NODE_ENV=developmentLOG_LEVEL=debugAPI_TIMEOUT=30000NODE_ENV=stagingLOG_LEVEL=infoDATABASE_URL=postgresql://staging-db.example.com:5432/mydb_stagingAPI_URL=https://api.staging.myapp.com# .env.production (осторожно — не коммитить с реальными значениями!)NODE_ENV=productionLOG_LEVEL=warnDATABASE_URL=postgresql://prod-db.example.com:5432/mydbAPI_URL=https://api.myapp.comSecrets в GitHub Actions
Заголовок раздела «Secrets в GitHub Actions»# В Settings → Secrets and variables → Actions# Добавляем: DATABASE_URL, JWT_SECRET и т.д.
jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy env: DATABASE_URL: ${{ secrets.DATABASE_URL }} JWT_SECRET: ${{ secrets.JWT_SECRET }} run: | # Переменные доступны в run echo "Deploying with DB: ${DATABASE_URL%%@*}@..."
- name: Set env from secret run: echo "API_KEY=${{ secrets.API_KEY }}" >> $GITHUB_ENV
- name: Use env from previous step run: echo "API Key set: ${API_KEY:0:4}****"Vercel и Railway: управление env vars
Заголовок раздела «Vercel и Railway: управление env vars»# Vercel CLIvercel env add DATABASE_URL productionvercel env add JWT_SECRET productionvercel env pull .env.local # скачать текущие
# Railway CLIrailway variables set DATABASE_URL="postgresql://..."railway variables listРотация секретов
Заголовок раздела «Ротация секретов»# При компрометации ключа:# 1. Сгенерировать новыйopenssl rand -hex 32
# 2. Обновить в всех местах:# - GitHub Secrets# - Vercel/Railway/etc# - .env.production (зашифрованный)# - Kubernetes secrets (если используется)
# 3. Деплоить новую конфигурацию
# 4. Отозвать старый ключ (если применимо)Проверочный список при деплое
Заголовок раздела «Проверочный список при деплое»#!/bin/bashREQUIRED_VARS=( "DATABASE_URL" "REDIS_URL" "JWT_SECRET" "SMTP_HOST")
MISSING=()for var in "${REQUIRED_VARS[@]}"; do if [ -z "${!var}" ]; then MISSING+=("$var") fidone
if [ ${#MISSING[@]} -ne 0 ]; then echo "❌ Missing required environment variables:" for var in "${MISSING[@]}"; do echo " - $var" done exit 1fi
echo "✅ All required environment variables are set"Ключевые моменты
Заголовок раздела «Ключевые моменты»- Никогда не хардкоди секреты в коде
.env.localи.env.production— добавляй в.gitignore.env.example— коммить как документацию- Валидируй env vars при старте приложения (zod отлично подходит)
NEXT_PUBLIC_префикс — только для не-секретных публичных значений- GitHub Secrets — для CI/CD
- Храни production секреты в менеджерах секретов (следующий урок)
Интерактивный пример
Заголовок раздела «Интерактивный пример»Как переменные окружения работают в разных средах: