Los archivos .env permiten separar configuraciones y secretos del código fuente. Son útiles para claves de API, URLs de bases de datos, puertos y modos de ejecución. Sin embargo, es común que una variable devuelva None, que el archivo no se encuentre, que se cargue desde otra carpeta o que un valor del sistema tenga prioridad sobre el archivo. Resolver estos problemas exige comprender cómo se localiza y carga la configuración.
Antes de continuar, consulta la guía para leer variables de entorno de forma segura, el artículo sobre pathlib, la explicación de try y except, la guía de logging y el tutorial de entornos virtuales.
Las fuentes externas principales son la documentación oficial de python-dotenv y la documentación oficial de os.environ.
Instalar python-dotenv en el entorno correcto
python -m pip install python-dotenv
python -m pip show python-dotenv
Si el import falla, verifica el intérprete activo:
import sys
print(sys.executable)
Un error habitual es instalar el paquete globalmente y ejecutar el proyecto desde otro entorno virtual.
Crear el archivo .env
APP_ENV=development
DEBUG=false
PORT=8000
API_KEY=valor_de_ejemplo
No añadas espacios innecesarios alrededor del nombre. Los valores pueden ir entre comillas cuando contienen espacios o caracteres especiales. Evita escribir comentarios al final de un secreto si no estás seguro de cómo será interpretado.
Cargar variables básicas
import os
from dotenv import load_dotenv
load_dotenv()
entorno = os.getenv("APP_ENV")
puerto = os.getenv("PORT")
print(entorno, puerto)
os.getenv() devuelve strings o None. No convierte automáticamente números ni booleanos.
Por qué os.getenv devuelve None
Las causas más comunes son un nombre diferente, archivo en otra carpeta, proceso iniciado desde otro directorio, llamada a load_dotenv() demasiado tarde o archivo guardado como .env.txt. Imprime únicamente información no sensible:
from pathlib import Path
print("Directorio actual:", Path.cwd())
print("Existe .env:", Path(".env").is_file())
print("APP_ENV definida:", os.getenv("APP_ENV") is not None)
Nunca imprimas el valor de una clave real para depurar.
Usar una ruta explícita
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).resolve().parent
RUTA_ENV = BASE_DIR / ".env"
load_dotenv(RUTA_ENV)
Este patrón no depende del directorio desde el que se ejecuta el comando. En proyectos con carpeta src, puede ser necesario utilizar parent para llegar a la raíz.
Localizar el archivo con find_dotenv
from dotenv import find_dotenv, load_dotenv
ruta = find_dotenv()
if not ruta:
raise FileNotFoundError("No se encontró el archivo .env")
load_dotenv(ruta)
find_dotenv() busca en directorios superiores. Es útil, pero una ruta explícita suele ser más predecible en servicios y tareas programadas.
Entender la prioridad de valores
load_dotenv(override=False)
Por defecto, una variable ya definida en el entorno del proceso suele conservarse. Esto permite que producción configure valores reales sin ser reemplazada por un archivo local. Para pruebas controladas puedes utilizar:
load_dotenv(RUTA_ENV, override=True)
No actives override=True sin analizar, porque podría sustituir secretos o configuraciones proporcionadas por el sistema.
Convertir booleanos correctamente
def leer_booleano(nombre, predeterminado=False):
valor = os.getenv(nombre)
if valor is None:
return predeterminado
normalizado = valor.strip().casefold()
verdaderos = {"1", "true", "yes", "si", "sí", "on"}
falsos = {"0", "false", "no", "off"}
if normalizado in verdaderos:
return True
if normalizado in falsos:
return False
raise ValueError(f"{nombre} no es un booleano válido")
bool("false") devuelve True porque cualquier string no vacío es verdadero. Por eso debes interpretar el texto explícitamente.
Convertir enteros con límites
def leer_entero(nombre, predeterminado=None, minimo=None, maximo=None):
texto = os.getenv(nombre)
if texto is None:
if predeterminado is None:
raise RuntimeError(f"Falta {nombre}")
return predeterminado
try:
valor = int(texto)
except ValueError as error:
raise ValueError(f"{nombre} debe ser entero") from error
if minimo is not None and valor < minimo:
raise ValueError(f"{nombre} debe ser al menos {minimo}")
if maximo is not None and valor > maximo:
raise ValueError(f"{nombre} no puede superar {maximo}")
return valor
Exigir variables obligatorias
def requerida(nombre):
valor = os.getenv(nombre)
if valor is None or not valor.strip():
raise RuntimeError(f"Falta la variable obligatoria {nombre}")
return valor
API_KEY = requerida("API_KEY")
El programa debe fallar al iniciar con un mensaje claro, en lugar de descubrir la ausencia durante una solicitud importante.
No incluir .env en Git
# .gitignore
.env
.env.*
!.env.example
Crea un archivo .env.example sin secretos:
APP_ENV=development
DEBUG=false
PORT=8000
API_KEY=
Este ejemplo documenta las variables necesarias. Si un secreto ya fue publicado, eliminarlo del repositorio no es suficiente: debes revocarlo y generar uno nuevo.
Evitar secretos en logs
import logging
logging.info("Configuración cargada")
logging.info("API_KEY definida: %s", API_KEY is not None)
No registres diccionarios completos de entorno, URLs con credenciales ni encabezados de autenticación.
Usar dotenv_values sin modificar el entorno
from dotenv import dotenv_values
config_archivo = dotenv_values(RUTA_ENV)
print(config_archivo.get("APP_ENV"))
dotenv_values() devuelve un diccionario. Es útil para inspección, pruebas y combinación explícita de capas:
config = {
**dotenv_values(".env.shared"),
**dotenv_values(".env.local"),
**os.environ,
}
La última capa tiene prioridad. Documenta este orden para evitar sorpresas.
Separar configuraciones por entorno
Puedes mantener archivos de ejemplo o desarrollo, pero los secretos de producción deberían administrarse mediante el sistema de despliegue o un gestor de secretos. No copies automáticamente un archivo local a un servidor.
Problemas con IDEs y tareas programadas
VS Code, PyCharm, cron y Task Scheduler pueden iniciar el programa desde directorios distintos. Una ruta basada en __file__ evita depender de la carpeta de trabajo. También verifica qué usuario ejecuta el proceso y qué variables tiene disponibles.
Probar la configuración
def cargar_configuracion():
return {
"entorno": os.getenv("APP_ENV", "development"),
"debug": leer_booleano("DEBUG", False),
"puerto": leer_entero("PORT", 8000, 1, 65535),
"api_key": requerida("API_KEY"),
}
En pruebas, utiliza variables ficticias y restaura el entorno después de cada caso. Nunca uses secretos reales en el conjunto de pruebas.
Errores frecuentes
Los fallos habituales son guardar el archivo como .env.txt, ejecutar desde otra carpeta, llamar load_dotenv() después de leer variables, convertir "false" con bool(), forzar override=True y subir secretos a Git. También es común imprimir el valor secreto durante el diagnóstico.
Conclusión
Los errores de .env se resuelven controlando ruta, orden de carga, nombres y conversiones. Utiliza una ubicación explícita, valida variables obligatorias y transforma números y booleanos con funciones seguras. Mantén secretos fuera del repositorio y de los logs. En producción, permite que el entorno del sistema tenga prioridad y utiliza un gestor de secretos cuando el proyecto lo requiera.






