Explicación de las variables de entorno: .Archivos, secretos y configuración env bien hechos
Las variables de entorno permiten que el mismo código se ejecute en una computadora portátil, en CI y en producción con diferentes configuraciones. Así es como se comportan realmente, con las sorpresas que nos encontramos al probarlos.
- Variables de entorno son configuraciones de valores-clave que un proceso recibe de su entorno.
- Mantienen la configuración, como los puertos y las URL de bases de datos, fuera de su código.
- Los valores son siempre cadenas, por lo que “falso” es verdadero a menos que lo analices.
- Nunca confirme
.envarchivos con secretos reales y valide la configuración requerida cuando se inicie la aplicación.
Su aplicación necesita una URL de base de datos. En su computadora portátil es una base de datos local, en CI una desechable y en producción un clúster administrado. Codificar cualquiera de ellos significa cambiar el código para cada entorno. Variables de entorno resuelven esto. El código dice DATABASE_URL y cada entorno proporciona su propio valor.
Esta idea es fundamental para el método de la aplicación Twelve-Factor, que dice que la configuración que varía entre implementaciones pertenece al entorno. Probamos el comportamiento siguiente con Python 3.13 en Windows. Cada resultado que se muestra es de esa ejecución.
¿Qué son las variables de entorno?
Cada proceso en ejecución tiene un conjunto de valores de texto con nombre llamado entorno. Ejemplos familiares son PATH, que le dice al shell dónde encontrar programas, y HOME o USERPROFILE. Su propia aplicación puede utilizar el mismo mecanismo para configuraciones como APP_PORT, LOG_LEVEL o DATABASE_URL.
Configuración de variables de entorno
| Concha | Para esta sesión | por un comando |
|---|---|---|
| bash/zsh | exportar APP_PORT=8080 |
APP_PORT=8080 aplicación Python.py |
| PowerShell | $entorno:APP_PORT = "8080" |
Configúrelo, ejecútelo y luego elimínelo |
| cmd de Windows | establecer APP_PORT=8080 |
No incorporado |
Estos duran sólo para la sesión de terminal actual. Para hacerlos permanentes, agréguelos a un perfil de shell como ~/.bashrc o, en Windows, use la configuración del entorno del sistema o setx. Para las aplicaciones, normalmente es mejor proporcionarlas en el momento de la implementación en lugar de configurarlas en todo el sistema.
Leerlos en código
import os
port = os.environ.get("APP_PORT", "8000") # default if missing
print(port) # 8000
db = os.environ["DATABASE_URL_MISSING_DEMO"]
# KeyError: 'DATABASE_URL_MISSING_DEMO'
Utilice .get() con un valor predeterminado para configuraciones opcionales. Utilice corchetes, que levantan KeyError, para las configuraciones sin las cuales la aplicación no puede funcionar. Otros lenguajes son similares: process.env.APP_PORT en Node.js, System.getenv("APP_PORT") en Java y os.Getenv("APP_PORT") en Go.
todo es una cuerda
Esto atrapa a personas en todos los idiomas. Configuramos un puerto y luego le agregamos “1”:
os.environ["APP_PORT"] = "8080"
port = os.environ["APP_PORT"]
print(type(port).__name__, repr(port), port + "1")
# str '8080' 80801
os.environ["APP_PORT"] = 8080
# TypeError: str expected, not int
El valor regresó como texto y al agregarlo se produjo 80801, no 8081. Convierta explícitamente, como en int(os.environ["APP_PORT"]). Los booleanos son peores:
os.environ["DEBUG"] = "false"
print(bool(os.environ["DEBUG"])) # True
Cualquier cadena que no esté vacía es verdadera en Python, por lo que "false" se convirtió en True. El modo de depuración se habría activado en producción. Analizar valores booleanos deliberadamente:
def env_bool(name, default=False):
value = os.environ.get(name)
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "on"}
env_bool("DEBUG") # False
env_bool("NOT_SET", default=True) # True
Cómo los heredan los procesos
Un proceso hijo recibe una copia del entorno de su padre. Iniciamos procesos secundarios de Python para ver esto:
child sees: 8080 inherited # default: full copy of the parent's environment
child sees: 9000 None # given a custom env: only what we passed
parent sees child var: None # a child's changes never flow back
Siguen tres lecciones prácticas. Las variables que exportas son visibles para todos los programas que inicias, incluidos los scripts que no escribiste, lo cual es importante para los secretos. Pasar un entorno personalizado reemplaza todo, así que incluya todo lo que el niño necesite. Y un script no puede cambiar las variables de su shell principal, razón por la cual las herramientas le piden source un archivo en lugar de ejecutarlo.
archivos .env
Escribir muchas variables a mano resulta tedioso, por lo que la mayoría de los proyectos mantienen los valores de desarrollo local en un archivo .env:
# local development only
APP_PORT=8080
DATABASE_URL="postgres://dev:devonly@localhost:5432/shop"
FEATURE_SEARCH=true
Bibliotecas como python-dotenv y dotenv para Node.js cargan este archivo al inicio. Node.js 20.6 y posteriores también pueden cargar uno de forma nativa con --env-file. Para ver la idea, escribimos un pequeño analizador que convirtió el archivo de arriba en:
{'APP_PORT': '8080',
'DATABASE_URL': 'postgres://dev:devonly@localhost:5432/shop',
'FEATURE_SEARCH': 'true'}
Utilice una biblioteca real en la práctica. Maneja citas, escapes, comentarios y valores de varias líneas que nuestra versión de 10 líneas ignora. De forma predeterminada, la mayoría de los cargadores dotenv no anulan las variables que ya están configuradas, por lo que los valores reales del entorno prevalecen sobre el archivo. Normalmente eso es lo que quieres.
Consejo: Agregue .env a .gitignore antes de su primera confirmación y, en su lugar, confirme un .env.example con cada nombre de variable y valores de marcador de posición seguros. Los nuevos desarrolladores lo copian y completan sus propios valores.
Validar la configuración al inicio
Una variable faltante debería detener la aplicación inmediatamente con un mensaje claro, no causar un error confuso una hora después. Verificamos las configuraciones requeridas al cargar:
REQUIRED = ["DATABASE_URL", "SECRET_KEY"]
def check_required(env):
missing = [k for k in REQUIRED if not env.get(k)]
if missing:
raise SystemExit(f"Missing required settings: {', '.join(missing)}")
# Missing required settings: SECRET_KEY
Las bibliotecas de configuración escritas van más allá. Las configuraciones de Pydantic en Python, zod o envalid en Node.js y las propiedades de configuración de Spring en Java convierten tipos e informan todos los problemas a la vez.
Variables de entorno y secretos.
Las variables de entorno son la forma común de pasar secretos, como claves API, a una aplicación, y son mucho mejores que codificarlas. Tienen debilidades que deben conocer:
- Los procesos secundarios los heredan, como demostró nuestra prueba.
- Pueden aparecer en informes de fallos, páginas de depuración y registros que volcan el entorno.
- En algunos sistemas, otros procesos ejecutados por el mismo usuario pueden leerlos.
Para la producción, muchos equipos guardan secretos en un administrador dedicado, como HashiCorp Vault, AWS Secrets Manager, Azure Key Vault o Google Secret Manager, y los inyectan en tiempo de ejecución. Algunos prefieren montar secretos como archivos en lugar de variables. De cualquier manera, nunca imprima la configuración en los registros y rote cualquier secreto que alguna vez se haya enviado a Git, incluso después de eliminarlo del historial.
En Docker y CI
# Docker (not run for this article)
docker run -e APP_PORT=8080 --env-file .env.production myapp
# GitHub Actions (not run for this article)
env:
APP_PORT: 8080
API_KEY: ${{ secrets.API_KEY }}
Las plataformas de CI almacenan secretos cifrados y los enmascaran en registros. Evite incluir secretos en imágenes de Docker con ENV en el Dockerfile, porque permanecen legibles en las capas de la imagen. Consulte nuestro tutorial de Docker Compose y Guía de CI/CD de GitHub Actions para configuraciones completas.
Mejores prácticas
- Utilice nombres claros en mayúsculas con un prefijo, como
SHOP_DB_URL. - Lea todas las configuraciones en un módulo, convierta los tipos allí y valide al inicio.
- Proporcione valores predeterminados seguros solo para configuraciones no confidenciales.
- Mantenga
.envfuera de Git y mantenga un.env.example. - Utilice un administrador de secretos en producción para valores confidenciales.
- Documente cada variable, su tipo y un valor de ejemplo.
Depuración de problemas de variables de entorno
Cuando una aplicación se comporta como si le faltara una configuración, realice estas comprobaciones en orden:
- ¿Está configurada en el lugar correcto? Una variable exportada en una pestaña del terminal no existe en otra o en un servicio iniciado por el sistema.
- ¿Se reinició la aplicación? Un proceso en ejecución mantiene el entorno con el que comenzó. Cambiar una variable posteriormente no tiene ningún efecto hasta el reinicio.
- ¿El nombre es exacto? Los nombres distinguen entre mayúsculas y minúsculas en Linux y macOS, por lo que
App_PortyAPP_PORTson variables diferentes. Windows los trata como iguales. - ¿Un archivo .env lo anula o se ignora? Compruebe si su cargador anula los valores existentes y qué archivo lee realmente.
- ¿Hay comillas o espacios perdidos? Un valor copiado con un espacio al final o comillas adicionales puede no poder analizarse o no coincidir, de maneras que son difíciles de detectar.
Para ver lo que realmente recibió un proceso, imprima los nombres de las variables, nunca los valores de los secretos. En Linux y macOS, printenv enumera el entorno del shell actual y en PowerShell Get-ChildItem Env: hace lo mismo. Dentro de un contenedor, ejecutar env a docker exec muestra la vista del contenedor, que a menudo es diferente de la de su propio shell.
Un último consejo: registre una línea al inicio que enumere qué configuraciones opcionales utilizan sus valores predeterminados. No cuesta nada y la mayoría de las respuestas son “¿por qué hace eso?” preguntas de inmediato.
Conclusión
Las variables de entorno son simples, pero esconden algunas trampas: todo es una cadena, los niños las heredan y los secretos pueden filtrarse a través de ellas. Trátelos como una entrada para su aplicación. Léalos en un solo lugar, conviértalos y valídelos con anticipación y mantenga secretos reales fuera de los archivos que se confirman.
Preguntas frecuentes
¿Qué es una variable de entorno?
Es un valor de texto con nombre que un proceso recibe de su entorno, como APP_PORT=8080. Las aplicaciones los usan para configuraciones que cambian entre entornos.
¿Qué es un archivo .env?
Un archivo de texto de líneas KEY=value, cargado al inicio por una biblioteca como python-dotenv. Se utiliza principalmente para el desarrollo local y no debe revelarse con verdaderos secretos.
¿Por qué mi variable de entorno es una cadena?
Los sistemas operativos almacenan los valores del entorno como texto. Convierta números con int() y analice booleanos explícitamente, porque la cadena “falso” cuenta como verdadera.
¿Las variables de entorno son seguras para los secretos?
Son mejores que la codificación fija, pero los procesos secundarios los heredan y los registros pueden exponerlos. Los sistemas de producción suelen utilizar un administrador de secretos dedicado.
¿Puede un script cambiar las variables de su shell principal?
No. Un proceso hijo obtiene una copia del entorno, por lo que sus cambios no regresan. Para establecer variables en el shell actual, obtenga el archivo en su lugar.
sigue aprendiendo
¿Necesitas ayuda con pruebas, WordPress o SEO?
Ayudamos a los equipos a enviar mejor software y hacer crecer sus sitios. Cuéntenos en qué está trabajando y le sugeriremos un siguiente paso claro.
Más guías sobre testing, IA y desarrollo moderno
¿Te gustó este artículo? Elige un tema y salta directo a nuestros tutoriales más recientes.
Explicación de las alucinaciones de la IA: por qué los modelos de lenguaje inventan cosas y cómo reducirlas
Se explicaron las alucinaciones de la IA: qué son, por qué los modelos de lenguaje las producen, tipos comunes, cómo reducirlas con conexión a tierr…
Leer artículo →DevOpsExplicación de las variables de entorno: archivos .env, secretos y configuración bien hechos
Variables de entorno explicadas con ejemplos de Python que ejecutamos: leerlas y configurarlas, por qué cada valor es una cadena, archivos .env, her…
Leer artículo →Desarrollo de softwareExplicación de la limitación de velocidad de API: algoritmos, encabezados y manejo de errores 429
La limitación de la tasa de API se explica con una simulación que ejecutamos: depósito de tokens, ventana fija, ventana deslizante, 429 respuestas, …
Leer artículo →Automatización de pruebasBurlarse en Python: unittest.mock, patch y los errores que hacen que las pruebas mientan
Burlarse en Python con unittest.mock, probado: Mock y return_value, side_effect, parches donde se usa un nombre, especificación y autospec, fechas d…
Leer artículo →Pruebas de softwareExplicación de la cobertura del código: cobertura de línea versus sucursal y lo que realmente significa 100%
Cobertura del código explicada con una ejecución real de cobertura.py: cobertura de línea, rama y condición, lectura de un informe, por qué una cobe…
Leer artículo →Bases de datos y SQLDELETE vs TRUNCATE vs DROP en SQL: diferencias, reversión y cuándo usar cada uno
DELETE vs TRUNCATE vs DROP explicado con SQL probado: qué elimina cada uno, reversión, activadores, restablecimiento de identidad, velocidad y en qu…
Leer artículo →Bases de datos y SQLVistas SQL explicadas: cree, use y evite los errores, con ejemplos
Vistas SQL explicadas con consultas que ejecutamos: creación de una vista, resultados en vivo, ocultación de columnas, rendimiento, vistas actualiza…
Leer artículo →Desarrollo de softwareInterfaz TypeScript versus tipo: diferencias reales, probadas con el compilador
Interfaz TypeScript versus tipo, verificado con el compilador real: qué pueden hacer ambos, qué puede hacer solo uno, fusión de declaraciones, exten…
Leer artículo →Desarrollo de softwarePython *args y **kwargs explicados: funciones flexibles con ejemplos claros
Python *args y **kwargs explicados con el código que ejecutamos: empaquetado y desempaquetado, orden de parámetros, argumentos solo de palabras clav…
Leer artículo →Desarrollo de softwareFunciones Lambda de Python: sintaxis, usos reales y cuándo no utilizarlas
Funciones lambda de Python explicadas con el código que ejecutamos: sintaxis, clasificación con clave, mapa y filtro, cierres, trampa de enlace tard…
Leer artículo →Inteligencia artificial¿Qué son los agentes de IA? Cómo funcionan, con un bucle de agente simple que puedes ejecutar
¿Qué son los agentes de IA? Una guía sencilla sobre el bucle del agente, las herramientas, la memoria y las barreras de seguridad, con un pequeño bu…
Leer artículo →DevOpsMonitoreo versus observabilidad: la verdadera diferencia, con registros, métricas y seguimientos
Explicación del monitoreo versus observabilidad: fallas conocidas y desconocidas, registros, métricas y seguimientos, un recorrido por incidentes de…
Leer artículo →DevOpsDocker Volumes versus Bind Mounts: persistir los datos del contenedor de la manera correcta
Se explican los volúmenes de Docker frente a los montajes de enlace: por qué desaparecen los datos del contenedor, volúmenes con nombre, montajes de…
Leer artículo →Automatización de pruebasTDD vs BDD: diferencias clave, una función creada en ambos sentidos y cuándo usar cada una
TDD vs BDD explicado con una característica de descuento construida en ambos sentidos: refactor rojo-verde con el código que ejecutamos, escenarios …
Leer artículo →Desarrollo de softwareGit merge vs rebase: diferencias, ejemplos y cuándo usar cada uno
Git merge vs rebase explicado con salida real del log, una tabla comparativa, la regla de oro del rebase y un flujo de trabajo seguro para equipos.
Leer artículo →Desarrollo de softwareList comprehension en Python: sintaxis, ejemplos y cuándo evitarla
List comprehension en Python explicada: sintaxis, filtros, if-else, bucles anidados, tiempos medidos y cuándo es mejor un bucle for normal.
Leer artículo →