Переменные окружения на проде
Переменные на продакшене нужно хранить и передавать безопасно.
Где хранить
- Vercel — Settings → Environment Variables
- Docker —
.env.productionили через секреты - GitHub Actions — Secrets
- AWS — Secrets Manager, SSM
- HashiCorp Vault — для больших команд
Не в репозитории
Никогда не коммить .env с секретами. Только .env.example.
.env
.env.local
.env.production
.env*.local
На Vercel
Три окружения:
- Production — для main
- Preview — для PR
- Development — для
vercel dev
Разные значения для preview и prod — тестовые базы.
В Docker
Через --env-file
docker run --env-file .env.production my-app
Через environment в compose
services:
app:
environment:
- DATABASE_URL=${DATABASE_URL}
- AUTH_SECRET=${AUTH_SECRET}
Значения — из .env рядом с docker-compose.yml или из переменных окружения хоста.
Docker Secrets
secrets:
db_password:
file: ./secrets/db_password.txt
services:
app:
secrets:
- db_password
В контейнере доступно по /run/secrets/db_password.
Валидация переменных
Проверяй при старте:
// lib/env.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
NEXT_PUBLIC_API_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});
export const env = envSchema.parse(process.env);
Использование:
import { env } from '@/lib/env';
const db = new PrismaClient({ datasources: { db: { url: env.DATABASE_URL } } });
Приложение упадёт сразу, если переменная отсутствует — не в рантайме.
Клиентские переменные
Только NEXT_PUBLIC_*:
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
Значение подставляется на этапе сборки. Если меняешь — нужен rebuild.
Runtime переменные
Серверные переменные читаются в рантайме:
const secret = process.env.AUTH_SECRET;
Можно менять без пересборки (для Docker).
NEXT_PUBLIC на проде
Проблема: NEXT_PUBLIC_* фиксируются при сборке. Если у тебя Docker-образ и нужно менять URL без пересборки — есть трюки:
Подстановка в рантайме
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<head>
<script
dangerouslySetInnerHTML={{
__html: `window.__ENV__ = ${JSON.stringify({
API_URL: process.env.NEXT_PUBLIC_API_URL,
})}`,
}}
/>
</head>
<body>{children}</body>
</html>
);
}
Но обычно проще пересобрать образ.
Секреты
Не логируй:
console.log(process.env); // плохо
console.log(process.env.DATABASE_URL); // тоже плохо
Не возвращай клиенту:
return Response.json({ secret: process.env.AUTH_SECRET }); // опасно
Ротация
Меняй секреты периодически:
- Создай новый секрет
- Обнови переменную
- Перезапусти приложение
- Убедись, что всё работает
- Отзови старый
Итоги
- Переменные не в репозитории
- Разные окружения для preview и prod
- Валидация через Zod при старте
NEXT_PUBLIC_*— на этапе сборки- Серверные — в рантайме
- Не логируй секреты