Перейти к содержимому

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.local (не коммитить!)
DATABASE_URL=postgresql://localhost:5432/mydb_dev
REDIS_URL=redis://localhost:6379
JWT_SECRET=dev-secret-key-change-in-prod
SMTP_HOST=localhost
SMTP_PORT=1025
# .env.example (коммитить — документация)
DATABASE_URL=postgresql://localhost:5432/mydb
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key-here
SMTP_HOST=smtp.example.com
SMTP_PORT=587
.gitignore
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local
# Разрешаем коммитить
!.env.example
!.env.test
// Базовое использование
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) {
throw new Error('DATABASE_URL is required');
}
// Типизация и валидация с zod
import { 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);
.env.local
# Серверные (не доступны в браузере)
DATABASE_URL=postgresql://localhost:5432/mydb
JWT_SECRET=supersecret
# Публичные (доступны в браузере через process.env)
NEXT_PUBLIC_API_URL=https://api.myapp.com
NEXT_PUBLIC_ANALYTICS_ID=UA-XXXXX
// В server компонентах/API routes
const db = createClient({ url: process.env.DATABASE_URL }); // ✅
// В client компонентах
const apiUrl = process.env.NEXT_PUBLIC_API_URL; // ✅
const secret = process.env.JWT_SECRET; // ❌ undefined в браузере!
.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

// Ручная загрузка .env
import '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=development
LOG_LEVEL=debug
API_TIMEOUT=30000
.env.staging
NODE_ENV=staging
LOG_LEVEL=info
DATABASE_URL=postgresql://staging-db.example.com:5432/mydb_staging
API_URL=https://api.staging.myapp.com
Окно терминала
# .env.production (осторожно — не коммитить с реальными значениями!)
NODE_ENV=production
LOG_LEVEL=warn
DATABASE_URL=postgresql://prod-db.example.com:5432/mydb
API_URL=https://api.myapp.com
# В 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 CLI
vercel env add DATABASE_URL production
vercel env add JWT_SECRET production
vercel env pull .env.local # скачать текущие
# Railway CLI
railway 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. Отозвать старый ключ (если применимо)
scripts/check-env.sh
#!/bin/bash
REQUIRED_VARS=(
"DATABASE_URL"
"REDIS_URL"
"JWT_SECRET"
"SMTP_HOST"
)
MISSING=()
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
MISSING+=("$var")
fi
done
if [ ${#MISSING[@]} -ne 0 ]; then
echo "❌ Missing required environment variables:"
for var in "${MISSING[@]}"; do
echo " - $var"
done
exit 1
fi
echo "✅ All required environment variables are set"
  • Никогда не хардкоди секреты в коде
  • .env.local и .env.production — добавляй в .gitignore
  • .env.example — коммить как документацию
  • Валидируй env vars при старте приложения (zod отлично подходит)
  • NEXT_PUBLIC_ префикс — только для не-секретных публичных значений
  • GitHub Secrets — для CI/CD
  • Храни production секреты в менеджерах секретов (следующий урок)

Как переменные окружения работают в разных средах: