Publicado en

Variables de entorno en proyectos: gestión segura

Archivo .env con variables de configuración y advertencia de no incluirlo en el repositorio sobre fondo oscuro

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 .env llega 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=production desactiva 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.