Y ahora directo de casos de la vida real, crack. Cuando empezamos un proyecto en .NET, el lugar más natural para guardar la configuración es el archivo appsettings.json. Es cómodo, está bien organizado y permite centralizar la configuración de la aplicación. El problema no es usar appsettings.json; el problema comienza cuando empezamos a guardar en él información sensible, como contraseñas, API Keys o claves de producción.
Esa práctica suele parecer inofensiva durante el desarrollo, pero termina convirtiéndose en un problema cuando la aplicación llega a producción. Es frecuente encontrar proyectos donde las credenciales reales de la base de datos, las claves JWT o las API Keys terminan almacenadas dentro del appsettings.Production.json, y desde ahí pasan a formar parte de la imagen de Docker.
Puede parecer una decisión segura, especialmente si la imagen se almacena en un registro privado, pero en realidad introduce problemas tanto de seguridad como de operación.
El verdadero problema no es solo la seguridad
Guardar secretos dentro de la imagen significa que cada vez que cambie una contraseña o una clave de acceso tendrás que generar una nueva imagen y volver a desplegarla.
Esto rompe uno de los principios fundamentales de Docker: la inmutabilidad.
La idea detrás de una imagen de Docker es construirla una única vez y reutilizar exactamente esa misma imagen en todos los entornos (Desarrollo, QA, Staging y Producción). Lo único que debería cambiar entre ellos es la configuración.
Cuando las credenciales forman parte de la imagen, ese principio desaparece. Además, cualquiera que tenga acceso a la imagen podría inspeccionar su sistema de archivos y recuperar esos archivos de configuración.
En otras palabras: una imagen debería contener únicamente código y recursos de la aplicación, nunca secretos.
¿Cómo lo resuelve .NET?
Afortunadamente, el sistema de configuración de ASP.NET Core fue diseñado precisamente para este escenario.
Cuando la aplicación inicia, carga la configuración desde diferentes proveedores. Primero lee los archivos appsettings.json y posteriormente otros orígenes como las variables de entorno.
Esto significa que puedes mantener valores de ejemplo o de desarrollo en el archivo JSON y sobrescribir únicamente aquellos que cambian en producción mediante variables de entorno, sin modificar una sola línea de código.
Por ejemplo, si tu configuración es:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDb;"
}
}
En producción basta con definir la variable de entorno:
ConnectionStrings__DefaultConnection
.NET interpreta el doble guion bajo (__) como el separador : Utilizado en la jerarquía del archivo JSON.
Este mismo mecanismo funciona para cualquier configuración anidada, por ejemplo:
Jwt__SecretKeyAzureStorage__ConnectionStringRedis__ConnectionStringEmail__SmtpPassword
¿Y durante el desarrollo?
Una duda muy habitual es dónde guardar las credenciales cuando desarrollamos localmente.
La respuesta recomendada por Microsoft es utilizar User Secrets.
Esta característica permite almacenar información sensible fuera del proyecto, evitando que las contraseñas terminen dentro del repositorio Git o del propio appsettings.json.
Por ejemplo:
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=MyDb;..."
De esta manera cada desarrollador mantiene sus propias credenciales sin necesidad de compartirlas ni incluirlas en el código fuente.
Cómo inyectar la configuración en Docker
Si despliegas tus aplicaciones utilizando Docker Compose, puedes definir las variables de entorno directamente desde el archivo docker-compose.yml.
services:
mi-api:
image: myregistry.azurecr.io/bravedeveloper-api:v1.0
environment:
- ConnectionStrings__DefaultConnection=${PROD_DB_CONNECTION}
- Jwt__SecretKey=${PROD_JWT_SECRET}
Una práctica habitual es almacenar esos valores dentro de un archivo .env, que Docker Compose utiliza para resolver las variables.
Eso sí, conviene recordar que un archivo .env no cifra ni protege los secretos; simplemente facilita su gestión y evita que formen parte de la imagen. Por ese motivo nunca debería subirse al repositorio y siempre debe incluirse dentro del .gitignore.
Cuando el proyecto empieza a crecer
Para aplicaciones pequeñas o desplegadas en un VPS, las variables de entorno suelen ser más que suficientes.
Sin embargo, cuando el sistema crece y aparecen múltiples servidores, varios equipos o procesos frecuentes de rotación de credenciales, gestionar archivos .env termina convirtiéndose en una tarea difícil de mantener.
En ese punto suele ser recomendable ya abrir un poco la billetera y utilizar un gestor de secretos como Azure Key Vault, AWS Secrets Manager o Google Secret Manager.
La aplicación obtiene las credenciales durante el arranque mediante la identidad que tenga asignada, mientras que los secretos permanecen centralizados y pueden administrarse sin necesidad de reconstruir imágenes ni modificar los despliegues.
Además, estos servicios ofrecen ventajas adicionales como rotación de secretos, control de acceso, auditoría y administración centralizada de las credenciales.
Así que, en conclusión...
La gestión de secretos no tiene por qué ser complicada desde el primer día, estimado dev.
Para el desarrollo local, si la información no es sensible, utiliza appsettings.json; si contiene credenciales o datos sensibles, utiliza User Secrets.
Para despliegues con Docker, aprovecha las variables de entorno.
Y cuando la infraestructura crezca, da el salto a un gestor de secretos especializado.
La regla es sencilla: la imagen contiene el código, la configuración específica de cada entorno se inyecta al ejecutar la aplicación y los secretos nunca deberían viajar dentro de la imagen.
Y ahora que ya lo sabes, crack, comparte el conocimiento!
Créditos de imagen de portada: Foto de Michael Bader en Unsplash
