wandres.dev
VARIABLES DE ENTORNO · astro:env

Buenas prácticas: .env.example, rotación y entornos

La disciplina que sostiene todo lo anterior: mantener un .env.example versionado como mapa de variables sin secretos, ignorar los archivos reales para no commitear credenciales nunca, separar los secretos por entorno con el mínimo privilegio, y rotar las claves de forma periódica y de urgencia cuando se filtran, diseñando siempre para contener el daño y no solo para prevenirlo.

⏱ 15 min

Los mecanismos de las lecciones anteriores —prefijos, esquemas tipados, módulos separados, almacenes de plataforma— son necesarios pero no suficientes. La seguridad de las variables de entorno se juega, en última instancia, en hábitos: qué archivos versionas, qué credenciales reutilizas, cada cuánto rotas una clave y cómo reaccionas cuando una se filtra. Esta lección recoge la disciplina que convierte todos esos mecanismos en una práctica sostenible. La tesis que la atraviesa es incómoda pero liberadora: no diseñes suponiendo que tus secretos nunca se filtrarán, porque alguno lo hará. Diseña para que, cuando ocurra, el daño sea pequeño, contenido y reparable en minutos.

🎯 Al terminar esta lección sabrás
  • Mantener un .env.example versionado como documentación viva de las variables sin secretos.
  • Configurar .gitignore para que los archivos con valores reales nunca lleguen al repositorio.
  • Separar credenciales por entorno aplicando el mínimo privilegio a cada una.
  • Rotar secretos de forma periódica y de urgencia, y actuar bien ante una filtración.

.env.example: el mapa que sí se comparte

Los archivos .env con valores reales no se versionan, pero el equipo necesita saber qué variables existen para poner en marcha el proyecto. Ese hueco lo llena .env.example: una plantilla versionada que lista cada nombre de variable con un valor de relleno o vacío, sin un solo secreto de verdad. Es a la vez documentación de arranque y contrato social sobre qué configuración espera el proyecto.

# .env.example  -> se versiona, no lleva secretos reales
PUBLIC_API_URL=https://api.ejemplo.com
PORT=8787
API_SECRET=pon-aqui-tu-clave
DATABASE_URL=postgres://usuario:clave@localhost/db

Quien clona el proyecto copia .env.example a .env, rellena los valores reales y arranca. El ejemplo debe mantenerse sincronizado con el esquema de astro:env: cada vez que añades una variable al esquema, añádela también aquí. Así el .env.example y el esquema se refuerzan mutuamente —uno enumera los nombres para un humano que empieza, el otro los valida para la máquina— y juntos forman una descripción completa de la superficie de configuración del proyecto.

💡
El esquema y el .env.example se documentan entre sí

Tienes dos fuentes de verdad complementarias, no redundantes. El esquema en astro.config.mjs dice el tipo, el contexto y el acceso de cada variable, y la máquina lo hace cumplir. El .env.example da nombres y ejemplos legibles a quien acaba de llegar, sin obligarle a leer la configuración. Mantener los dos alineados cuesta segundos y evita el clásico funciona en mi máquina, ese fallo que nace siempre de una variable que alguien tenía en su .env local y nadie más sabía que hacía falta.

ℹ️
Genera el ejemplo desde el esquema si puedes

Como el esquema ya enumera todas las variables, puedes derivar de él un .env.example en lugar de mantenerlo a mano, cerrando la única grieta por la que ambos se desincronizan. Cuando la plantilla nace del mismo sitio que la validación, es imposible que una prometa una variable que la otra ignora, y quien clona el proyecto recibe siempre una lista fiel a lo que el código espera. Un paso manual menos es un descuido menos que cometer.

Nunca commitear un secreto, y qué hacer si pasó

La regla es absoluta: los archivos con valores reales quedan fuera del control de versiones. Tu .gitignore debe cubrir el .env y sus variantes locales, mientras deja pasar la plantilla de ejemplo, que es justo lo que sí quieres compartir.

# .gitignore
.env
.env.local
.env.*.local
# .env.example NO se ignora: es la plantilla publica

Aun con esa red, un secreto se cuela alguna vez —un git add con comodín, un archivo mal nombrado—. Cuando ocurre, hay una verdad que asumir sin negociar: el secreto está comprometido. Borrarlo del último commit no basta, y reescribir el historial tampoco lo salva, porque en el intervalo en que estuvo publicado cualquiera pudo copiarlo, y los rastreadores automáticos que peinan los repositorios lo hacen en segundos. La única respuesta correcta es rotar: invalidar de inmediato la credencial filtrada y emitir una nueva. Limpiar el historial es higiene posterior; rotar es la cura.

⚠️
Reescribir el historial no rota el secreto

Es un error peligroso creer que borrar el archivo del historial de git deja el secreto a salvo. Un secreto que estuvo publicado, aunque fuera un minuto, hay que darlo por conocido por un tercero. La operación sobre el historial reduce la exposición futura, pero no rescata la credencial ya vista: esa hay que revocarla en su almacén de origen y sustituirla por otra. Invertir el orden —limpiar el historial y confiar en que nadie lo vio— es la forma más común de una brecha que sigue abierta mientras crees haberla cerrado.

💡
Ensaya la rotación antes de necesitarla

Rotar un secreto por primera vez en mitad de un incidente, con las manos temblando, es la peor forma de descubrir que no sabías cómo. Practica la rotación cuando no pasa nada: genera una credencial nueva, actualízala en el almacén del entorno y confirma que el sitio sigue en pie sin recompilar. Un procedimiento ensayado convierte una urgencia en un trámite de cinco minutos, y esa calma es justo lo que necesitas el día en que la filtración es real.

Separar, rotar y verificar

Un solo secreto compartido por desarrollo, pruebas y producción es una bomba de radio amplio: si se filtra el de un becario en su portátil, cae producción entera. La práctica sana es lo contrario. Cada entorno tiene sus propias credenciales, y cada credencial lleva el mínimo privilegio que su tarea exige. La clave de desarrollo apunta a recursos de desarrollo y no puede tocar los datos reales; la de producción vive solo en el almacén de producción y jamás baja a la máquina de nadie.

💡
El mínimo privilegio limita el radio de una fuga

Dar a cada credencial solo los permisos que su tarea exige no es burocracia, es contención. Una clave de solo lectura que se filtra no puede borrar nada; un token restringido a un recurso no abre los demás. Cuando algún día una credencial se escape —y alguna lo hará—, la diferencia entre un susto y una catástrofe será cuánto podía hacer esa llave concreta. Estrechar su alcance de antemano es decidir, con la cabeza fría, el tamaño máximo del daño futuro.

🗺️

.env.example versionado

Enumera cada variable con valores de relleno. Es la documentacion de arranque y se mantiene al dia con el esquema.

🚫

.gitignore estricto

Ignora .env y sus variantes locales, nunca la plantilla. El secreto real jamas toca el repositorio.

🧭

Un secreto por entorno

Credenciales distintas para desarrollo, pruebas y produccion, cada una con el minimo privilegio posible.

🔄

Rotación

Renueva las claves de forma periodica y de urgencia al filtrarse. Si viven fuera del artefacto, rotar es barato.

La rotación tiene dos ritmos. El periódico es preventivo: renuevas las claves cada cierto tiempo aunque nada haya pasado, porque una credencial que lleva años viva ha tenido años de oportunidades para filtrarse por vías que ni sospechas. El de urgencia es reactivo: en cuanto hay indicio de exposición, revocas y reemplazas sin dudar. Que rotar sea posible sin dolor es, precisamente, el fruto de todo lo que construimos en la lección anterior: como el secreto vive en el entorno y no en el artefacto, cambiarlo es actualizar un almacén, no reconstruir y redesplegar el mundo. La higiene se apoya en la arquitectura.

flowchart TD
SCHEMA[esquema y punto env example versionados] --> REPO[repositorio sin secretos]
DEV[almacen de desarrollo] --> APP[artefacto agnostico]
PROD[almacen de produccion] --> APP
APP --> RUN[ejecucion con secretos del entorno]
RUN --> ROT[rotacion periodica y de urgencia]
ROT --> DEV
ROT --> PROD
style REPO fill:#a6e3a1,color:#11111b
style APP fill:#89b4fa,color:#11111b
style ROT fill:#f9e2af,color:#11111b
📝
Automatiza la detección antes de que el humano falle

No confíes solo en la vigilancia manual. Un gancho de pre-commit o un rastreador de secretos en tu integración continua examina cada cambio en busca de patrones que parezcan credenciales y detiene el envío antes de que el secreto salga de la máquina. Es una red bajo la red: la disciplina humana falla en un día de prisas, y esa es exactamente la ocasión en la que una comprobación automática, que nunca tiene prisa, gana su sitio. Combinar el hábito con la herramienta es más robusto que confiar en cualquiera de los dos por separado.

Y a esa detección automática conviene sumar la comprobación deliberada, porque la disciplina se sostiene mejor cuando se verifica que cuando se confía en el proceso. Dos verificaciones baratas atrapan la mayoría de los descuidos antes de que lleguen lejos. La primera mira el artefacto: tras construir, rastrea la carpeta de cliente en busca de fragmentos de tus secretos, y si alguno aparece, tienes una fuga que corregir antes de desplegar.

# Busca en el build de cliente cualquier rastro de un secreto conocido
grep -r "fragmento-del-secreto" dist/client/ && echo "FUGA" || echo "limpio"

La segunda mira el repositorio: revisa qué archivos rastrea git y confirma que ningún .env con valores reales está entre ellos. Estas comprobaciones no sustituyen a los mecanismos de fondo —el prefijo, el esquema, el .gitignore—, pero los cierran con una mirada empírica que convierte creo que no filtro en he mirado y no filtro. En seguridad, esa distancia entre creer y haber comprobado es casi todo.

📝
Un grep limpio no siempre prueba ausencia

Cuidado con el método: buscar el secreto literal atrapa la fuga obvia, pero no la que viaja transformada —codificada, troceada o incrustada dentro de un JSON—. Por eso la búsqueda empírica complementa a los muros estructurales, pero no los sustituye: el prefijo y el esquema garantizan que el secreto no se incruste, y el grep solo confirma lo que ya debería ser cierto. Confía en la estructura y usa la búsqueda como una segunda mirada, nunca como la única.

Menos secretos, y de vida más corta

La mejor forma de proteger un secreto es no tener que guardarlo. Cada credencial estática de larga vida es un pasivo permanente: existe, hay que custodiarla y su probabilidad de filtrarse solo crece con el tiempo. Por eso la dirección en la que se mueve la industria es reducir su número y acortar su vida. Donde la plataforma lo permita, prefiere una identidad gestionada o un token efímero a una clave fija guardada a mano: en vez de un secreto eterno que copias entre entornos, el runtime obtiene una credencial de corta duración justo cuando la necesita y la descarta enseguida.

ℹ️
Un token que caduca se contiene a sí mismo

La virtud de una credencial de vida corta es que limita el daño por construcción, sin que tú hagas nada. Si se filtra, vale hasta que expira —minutos, horas— y no indefinidamente. Es la rotación llevada al extremo: en lugar de renovar a mano cada cierto tiempo, el sistema emite credenciales que ya nacen con fecha de caducidad. Cuando puedas elegir entre una clave estática que dura para siempre y un token que se renueva solo, el segundo te ahorra tanto la rotación periódica como buena parte del miedo a la filtración.

Este principio se combina con todo lo anterior en una jerarquía clara de preferencias. Lo ideal es no necesitar un secreto, resolviendo la autenticación con una identidad que la plataforma gestiona. Si hace falta un secreto, que sea de vida corta y ámbito estrecho. Y si ha de ser estático y duradero, que viva cifrado en el almacén del entorno, separado por entorno y rotado con disciplina. Cada escalón que subes en esa jerarquía añade una capa de contención, y ninguna sustituye a las demás: se acumulan.

⚠️
Los secretos pertenecen a servicios, no a personas

Evita atar credenciales a individuos concretos o compartir una misma cuenta entre todo el equipo: cuando alguien se va o su portátil se pierde, no deberías tener que rotar media infraestructura ni averiguar quién sabía qué. Una credencial con un dueño claro —este servicio, este entorno— es una que puedes revocar sin miedo a romper el trabajo de otro, y esa capacidad de revocar sin daño colateral es justo lo que quieres tener disponible el día del incidente.

Con esto cierra el nivel. Empezaste con un prefijo que Vite sustituye en el build, seguiste con un esquema que tipa y valida, aprendiste a leerlo desde los dos lados y a inyectar secretos en cada plataforma, y terminas con los hábitos que lo sostienen. El hilo que une las cinco lecciones es uno solo: la configuración es una frontera, y una frontera se cuida decidiendo temprano qué la cruza, tipando lo que entra, aislando lo sensible y asumiendo, con humildad, que algún día algo se escapará.

Diseña suponiendo la filtración: minimiza el radio, no solo la probabilidad

La madurez en el manejo de secretos llega el día en que dejas de pensar solo en evitar la filtración y empiezas a pensar en contenerla. Es un cambio de mentalidad profundo, porque el instinto nos empuja a la prevención: muros más altos, reglas más estrictas, más cuidado. La prevención es necesaria, pero tiene un techo cruel: basta un error, una vez, para anularla entera, y con secretos que viven años y pasan por decenas de manos, máquinas y registros, la probabilidad acumulada de que alguno se escape no tiende a cero, tiende a uno. El ingeniero maduro acepta esa aritmética y añade una segunda pregunta a la primera. No solo ¿cómo evito que se filtre?, sino ¿qué pasa el día que se filtre?. Y esa segunda pregunta reordena todas sus decisiones. Separa los entornos, para que la caída de uno no arrastre a los demás. Da a cada credencial el mínimo privilegio, para que la que se escape abra la puerta más pequeña posible. Hace las claves rotables y de vida corta, para que una filtrada valga solo hasta la próxima rotación. Mantiene el secreto fuera del artefacto, para que contenerlo sea cambiar el entorno y no reconstruir el mundo. Fíjate en el patrón: cada una de estas prácticas asume, sin dramatismo, que la primera línea cederá, y trabaja para que la segunda importe menos. Es defensa en profundidad aplicada a la configuración, y es también una forma de honestidad. Las variables de entorno no son un trámite de despliegue que se resuelve una tarde y se olvida: son la superficie por la que tu sistema toca todo lo que hay de valioso al otro lado —bases de datos, pasarelas de pago, servicios ajenos—, y tratarlas con el respeto de quien sabe que algún día una fallará es lo que distingue un proyecto que sobrevive a un incidente de uno que se hunde con él. Prevén con todas tus fuerzas; y aun así, diseña para el día en que la prevención no baste.

⚔️ Endurece el manejo de secretos de tu proyecto
  1. Crea un .env.example sincronizado con tu esquema de astro:env y verifica que .gitignore deja pasar la plantilla pero ignora el .env real.
  2. Sustituye cualquier secreto compartido entre entornos por credenciales distintas para desarrollo y producción, cada una con su alcance mínimo.
  3. Simula una filtración: da por comprometida una clave, rótala en su almacén y confirma que el sitio sigue funcionando sin recompilar.
  4. Añade a tu integración continua un rastreador de secretos y comprueba que bloquea un commit que contenga una credencial de prueba.