Usando variables de ambiente Docker en .NET Core

Algo realmente bueno acerca de empaquetar tus aplicaciones con Docker es que estamos compilando una vez y publicando muchas veces en distintos lugares (entornos), debido a esto, es una buena idea hacer la aplicación configurable a nivel de entorno (no solamente a nivel de archivos). Esto significa que deberiamos evitar tener archivos de configuración en el contenedor que puedan ser diferentes en cada entorno en el que se corra el contenedor. "La configuración de una aplicación es todo aquello que es probable que varíe en cada publicación" (The 12 Factor App).

¿Pero por qué variables de entorno?

La idea viene de la metodología "The 12 Factor App" (https://12factor.net/config), donde dice que debemos preferir usar este tipo de configuración porque tener archivos puede comprometer información confidencial (la seguridad en Docker no es tan madura todavía porque podemos inspeccionar fácilmente el contenedor y ver todas las variables de entorno, pero eso es una charla interesante para otro tiempo).

No voy a repetir lo que seguro ya leiste en el enlace anterior, pero quiero enfocarme en lo siguiente: "En una '12 Factor App', las variables de ambiente son controles granulares, cada uno totalmente ortogonal a otras variables de ambiente. Nunca son agrupadas juntas como un 'ambientes', sino que se manejan de forma independiente para cada publicación. Este es un modelo que escala sin problemas ya que la aplicación naturalmente se expande en más publicaciones a lo largo de su vida útil. (The 12 Factor App)

Por lo tanto, la idea es que siempre debemos tener nuestra aplicación preparada para comportarse de acuerdo a cada entorno, no deberíamos tener que volver a compilar nuestro código sólo porque tenemos que cambiar alguna configuración, imagínate hacer eso en el software que utilizamos en nuestras computadoras.

Eso es bueno, pero ¿Cómo se puede hacer esto en .NET Core?

Me alegra que lo preguntes. Para este momento espero que ya estes convencido que las variables de ambiente son una buena opción y para suerte nuestra, así es como Docker funciona, solo necesitamos saber cómo lograr este comportamiento en .NET Core. Para esto, también hay una buena documentación aquí, pero en este post me centraré sólo en cómo leer variables de ambiente que vienen de Docker.

.NET Core tiene una muy buena integración con variables de ambiente, de hecho la plantilla predeterminada para una Web API lo usa por defecto, así que solo necesitamos hacer algunos cambios en nuestro código en la clase Startup.cs, crear una nueva clase POCO y usarla en nuestros controladores inyectando la configuración que necesitamos.

Cambios en la aplicación

Mientras listo los cambios que tenemos que hacer en la aplicación, voy a dar una explicación en cada paso. He subido el código de muestra en Github, por lo que si no me logro explicar lo suficiente, puedes ver el código fuente aquí. Aquí está la lista de pasos:

var builder = nuevo ConfigurationBuilder()
  .SetBasePath (env.ContentRootPath)
  ... otras llamadas de métodos
  .AddEnvironmentVariables();
public class EnvironmentConfig
{
     public string ProfileName { get; set; }
}
services.Configure<EnvironmentConfig>(Configuration);
private readonly EnvironmentConfig _configuration;

public ValuesController(IOptions<EnvironmentConfig> configuration)
{
    _configuration = configuration.Value;
}

En caso de que no lo veas, una de las cosas geniales al hacerlo de esta manera es que si no especificas una variable de ambiente llamada "ProfileName" tu aplicación no se detendrá (a menos que quieras/necesites) porque cuando lea la propiedad de la clase de configuración esta estará vacía pues no hubo ningun match.

Y eso es todo, si desea profundizar en cómo la configuración puede ser modificada/utilizada en .NET Core por favor lee el siguiente artículo aquí

¿Cómo probar y depurar esto con VS?

Supongo que ya has agregado el soporte a Docker en tu solución, si no entonces dale un vistazo a este artículo. En la solución, abre el archivo docker-compose.override.yml dentro de la sección docker-compose y en 'environment', escribe la variable de ambiente para el nombre de perfil, algo asi:

env var docker compose

Ahora ve y debuggea en modo Docker, la aplicación debe leer ese valor desde el ambiente del contenedor.

Pero ¿qué pasa con Docker?

Sí, sé que casi me olvidé de esto. ¿Pero sabes que? Esto es realmente simple, voy a poner aquí la forma en que ejecuto mi contenedor para que puedas ver cómo definir una variable de ambiente que la aplicación en .NET Core leerá.

docker run -e ProfileName='Christian' -d -p 9000:80 christianhxc/hellodocker:env

Ahora vamos a la URL: http://localhost:9000/api/values y boom! Yo veo mi nombre allí. Por supuesto, no es necesario especificar todas las varialbes de ambiente con el comando 'run', puodemos hacer uso de docker compose para agruparlos, pero recuerda: no incluyas ningún archivo de configuración con el código fuente en el control de versiones.

No es necesario seguir esta guía para probarlo, simplemente ejecuta el comando anterior con valores diferentes :)