
Cómo gestionar variables de entorno en proyectos reales: separar la configuración del código, proteger los secretos y hacer que el mismo código funcione en development, staging y producción sin tocar una sola línea.
El archivo .env
# .env — NUNCA subir al repositorio
DB_HOST=localhost
DB_PORT=5432
DB_NAME=miapp
DB_USER=yassin
DB_PASSWORD=secreto_local
API_KEY=tu_clave_de_api
JWT_SECRET=cadena_larga_aleatoria_para_firmar_tokens
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
Lo primero: .gitignore
# .gitignore — añadir ANTES de hacer el primer commit
.env
.env.local
.env.*.local
# Verificar que no está trackeado
git status # no debe aparecer .env
git ls-files .env # debe devolver vacío
# Si ya lo has commiteado por error
git rm --cached .env
git commit -m "chore: eliminar .env del repositorio"
# IMPORTANTE: el historial aún lo contiene → rotar todos los secretos
Archivos de ejemplo: .env.example
# .env.example — SÍ subir al repositorio, sin valores reales
DB_HOST=localhost
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASSWORD=
API_KEY=
JWT_SECRET=
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
ALLOWED_ORIGINS=
# Al clonar el proyecto, el desarrollador crea su .env local
cp .env.example .env
# y rellena los valores reales en su máquina
Múltiples entornos
.env # valores por defecto / development
.env.local # sobreescribe .env, solo local (git ignored)
.env.test # para entorno de tests
.env.staging # valores de staging (sin secretos reales)
.env.production # no debería existir en el repo; usa secretos del CI
Cargar .env en distintos lenguajes
// Node.js — instalar: npm install dotenv
import 'dotenv/config'; // ES modules
// o
require('dotenv').config(); // CommonJS
console.log(process.env.DB_HOST); // → localhost
console.log(process.env.PORT ?? 3000); // con fallback
# Python — instalar: pip install python-dotenv
from dotenv import load_dotenv
import os
load_dotenv() # carga .env desde el directorio actual
db_host = os.getenv('DB_HOST', 'localhost') # con valor por defecto
api_key = os.environ['API_KEY'] # error si no existe
# Bash — cargar manualmente
set -a # hace que todas las variables asignadas sean exportadas
source .env
set +a
# O más selectivo:
export $(grep -v '^#' .env | xargs)
Variables en Docker
# docker-compose.yml
services:
api:
image: mi-api
env_file:
- .env # carga el archivo .env completo
environment:
NODE_ENV: production # sobreescribe o añade variables concretas
DB_HOST: db # el host es el nombre del servicio
db:
image: postgres:15
environment:
POSTGRES_DB: ${DB_NAME} # interpolación desde el .env del host
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
# Dockerfile: variables de build vs runtime
# ARG solo existe durante docker build
ARG NODE_ENV=production
# ENV existe en el contenedor en ejecución
ENV PORT=3000
# Pasar ARG en tiempo de build
docker build --build-arg NODE_ENV=staging -t mi-app .
Variables en CI/CD (GitHub Actions)
# GitHub Actions — secretos en Settings → Secrets
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
run: ./deploy.sh
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
API_KEY: ${{ secrets.API_KEY }}
SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
Validar variables al arrancar
// Validar al inicio: mejor fallar pronto que en producción
const required = ['DB_HOST', 'DB_PASSWORD', 'JWT_SECRET', 'API_KEY'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Variable de entorno requerida no definida: ${key}`);
}
}
// Con Zod (más robusto)
import { z } from 'zod';
const env = z.object({
DB_HOST: z.string().min(1),
DB_PORT: z.coerce.number().default(5432),
NODE_ENV: z.enum(['development', 'test', 'production']),
JWT_SECRET: z.string().min(32),
}).parse(process.env);
Buenas prácticas
- Nunca hardcodear secretos en el código: aunque sea en una rama privada. El historial de Git es permanente.
- Rotar secretos si se exponen: si un
.envllega al repo por error, cambiar todas las credenciales que contenía antes de hacer nada más. - Usar secretos del sistema CI para producción: las variables de producción no deben estar en ningún archivo del repositorio, ni siquiera en ramas privadas.
- Diferenciar desarrollo de producción:
NODE_ENV=productiondesactiva logs verbosos, activa optimizaciones y cambia comportamientos de seguridad en muchos frameworks. - Documentar en .env.example: si incorporas a alguien al proyecto, el archivo de ejemplo debe ser suficiente para que sepa qué necesita configurar.