BiblioTech ya tiene un dominio maduro, una arquitectura desacoplada, una API completa y una
batería de pruebas —unitarias y de integración— que confirman que todo funciona como se espera.
Queda un último paso, el que convierte todo ese trabajo en algo que un usuario real puede usar:
desplegarlo. Esta lección publica la API con dotnet publish, la empaqueta en un contenedor
Docker sencillo, configura su comportamiento de forma distinta según el entorno (desarrollo
frente a producción), y menciona, sin entrar en el detalle de ninguna plataforma concreta, cómo
encaja todo esto en un pipeline de integración y despliegue continuos. Es la última lección del
curso: cierra no solo el Módulo 9, sino el recorrido completo que empezó con un simple "Hola
Mundo" en la primera lección del Módulo 1.
Contenido
dotnet publish: de código fuente a artefacto desplegable- Contenedorización básica con Docker
- Configuración por entorno:
appsettings.jsony variables de entorno - La cadena de conexión de producción
- Consideraciones básicas de CI/CD
- BiblioTech, desplegada
dotnet publish: de código fuente a artefacto desplegable
dotnet publish: de código fuente a artefacto desplegableHasta ahora, cada lección del curso se ejecutó con dotnet run, que compila y arranca la
aplicación en un solo paso pensado para el desarrollo. dotnet publish genera, en cambio, la
versión lista para desplegar de la aplicación: los ficheros exactos que hacen falta para
ejecutarla en otra máquina, sin necesitar el código fuente ni el SDK completo de .NET, solo el
runtime:
-c Release: compila en modo Release en vez de Debug (el que se usa por defecto en desarrollo), con optimizaciones activadas y sin la información adicional que facilita depurar paso a paso (Lección 4) pero que no hace falta en producción.-o ./publicado: la carpeta de salida con el resultado: los ensamblados (.dll) deBiblioTech.Api,BiblioTech.DominioyBiblioTech.Persistencia, sus dependencias, y un ejecutable de entrada.
El resultado de ./publicado es exactamente lo que se copia a un servidor, o lo que se empaqueta
dentro de un contenedor Docker en el siguiente apartado: ya no hace falta el código fuente, ni
BiblioTech.Pruebas, ni ningún proyecto de desarrollo para ejecutar la aplicación desde ahí.
- Contenedorización básica con Docker
Docker empaqueta una aplicación junto con todo lo que necesita para ejecutarse (el runtime de
.NET, en este caso) en una unidad autocontenida llamada imagen, que se ejecuta de forma
idéntica en cualquier máquina con Docker instalado, sin importar qué más tenga instalado esa
máquina. Un Dockerfile sencillo para BiblioTech.Api:
# Etapa 1: compilar y publicar la aplicacion con el SDK completo de .NET
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /origen
COPY . .
RUN dotnet restore BiblioTech.Api
RUN dotnet publish BiblioTech.Api -c Release -o /publicado
# Etapa 2: la imagen final solo necesita el runtime, no el SDK completo
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /publicado .
EXPOSE 8080
ENTRYPOINT ["dotnet", "BiblioTech.Api.dll"]Este Dockerfile usa una técnica llamada compilación multietapa (multi-stage build): la
primera etapa (build) usa la imagen del SDK completo (más pesada) solo para compilar y publicar;
la segunda etapa (final), la que realmente se distribuye y ejecuta, parte de una imagen mucho
más ligera que solo trae el runtime de ASP.NET Core, sin las herramientas de compilación que ya no
hacen falta una vez generado /publicado. docker run -p 8080:8080 expone el puerto 8080 del
contenedor en el puerto 8080 de la máquina anfitriona, permitiendo acceder a la API exactamente
igual que si se ejecutara con dotnet run en local.
- Configuración por entorno:
appsettings.json y variables de entorno
appsettings.json y variables de entornoASP.NET Core distingue automáticamente entre entornos (desarrollo, producción...) y carga la configuración correspondiente combinando varios ficheros y fuentes, en un orden concreto de prioridad:
BiblioTech.Api/ ├── appsettings.json // configuracion base, comun a todos los entornos ├── appsettings.Development.json // solo se aplica en el entorno "Development" (dotnet run local) └── appsettings.Production.json // solo se aplica en el entorno "Production" (desplegado)
// appsettings.Development.json: mas detalle de logging en local
{
"ConnectionStrings": {
"BiblioTech": "Data Source=bibliotech-dev.db"
},
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}// appsettings.Production.json: sin cadena de conexion aqui (ver apartado 4)
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}| Fuente de configuración | Cuándo se aplica | Prioridad |
|---|---|---|
appsettings.json |
Siempre, como base | Más baja |
appsettings.{Entorno}.json |
Solo si ASPNETCORE_ENVIRONMENT coincide con {Entorno} |
Media |
| Variables de entorno | Siempre que estén definidas | Alta |
| Argumentos de línea de comandos | Solo si se pasan al ejecutar | La más alta |
Esta jerarquía permite, por ejemplo, tener valores de desarrollo cómodos por defecto en
appsettings.Development.json (una base SQLite local), mientras que en producción una variable
de entorno sobrescribe ese mismo valor sin tocar ningún fichero desplegado —justo lo que resuelve
el requisito no funcional de seguridad de la Lección 2 (no dejar datos sensibles en el código
fuente ni en los ficheros de configuración que se versionan).
- La cadena de conexión de producción
La cadena de conexión de la base de datos de producción (Módulo 5, Entity Framework Core sobre
SQLite, decisión de la Lección 2) es exactamente el tipo de dato que nunca debe escribirse
directamente en appsettings.Production.json ni en ningún fichero que se suba al control de
versiones. En su lugar, se define como variable de entorno en el servidor o en el propio
contenedor Docker:
export ConnectionStrings__BiblioTech="Data Source=/datos/bibliotech-produccion.db"
docker run -p 8080:8080 \
-e ConnectionStrings__BiblioTech="Data Source=/datos/bibliotech-produccion.db" \
-e ASPNETCORE_ENVIRONMENT=Production \
bibliotech-apiEl doble guion bajo (__) en ConnectionStrings__BiblioTech es la forma en que ASP.NET Core
traduce la jerarquía de una variable de entorno a la misma estructura anidada que tendría en JSON
("ConnectionStrings": { "BiblioTech": "..." }). El código de Program.cs que ya se escribió en
09-03 (builder.Configuration.GetConnectionString("BiblioTech")) no necesita ningún cambio: lee
siempre de la misma forma, sin importar si el valor final vino de un fichero JSON de desarrollo o
de una variable de entorno de producción. Esta es la misma idea de fondo que motivó
IRepositorioBiblioteca en el Módulo 8: el código que consume un valor no debería necesitar saber
de dónde viene exactamente ese valor.
- Consideraciones básicas de CI/CD
CI/CD (Continuous Integration/Continuous Deployment, integración y despliegue continuos) es la práctica de automatizar, mediante un pipeline, los pasos que van desde un cambio de código hasta su despliegue, en vez de ejecutarlos a mano cada vez. Un pipeline típico para BiblioTech, sin atarse a ninguna plataforma concreta (GitHub Actions, GitLab CI, Azure DevOps y otras ofrecen todas variantes de lo mismo), encadenaría estos pasos:
flowchart LR
A["Cambio de codigo<br/>(git push)"] --> B["Build<br/>dotnet build"]
B --> C["Test<br/>dotnet test<br/>(unitarias + integracion, Leccion 4)"]
C --> D{"Todo en verde?"}
D -->|"Si"| E["Publish<br/>dotnet publish + docker build"]
D -->|"No"| F["Pipeline se detiene<br/>sin desplegar nada roto"]
E --> G["Despliegue<br/>(plataforma concreta, fuera del alcance de esta leccion)"]
El valor central de este pipeline no es la automatización en sí misma, sino la garantía que aporta: ningún cambio llega a producción sin haber pasado, automáticamente y sin intervención manual, por exactamente las mismas pruebas que se ejecutaron en la Lección 4. El checklist de calidad de esa lección deja de depender de que alguien se acuerde de ejecutarlo a mano antes de cada despliegue.
- BiblioTech, desplegada
Con esto, BiblioTech completa su recorrido: una imagen Docker publicable con docker build,
configurada de forma distinta en desarrollo y en producción sin cambiar código, con su cadena de
conexión de producción fuera del código fuente, y con un camino claro (aunque no implementado en
detalle en este curso) hacia un pipeline de CI/CD que verifique automáticamente cada cambio antes
de desplegarlo. El sistema que empezó como un ejercicio de consola en el Módulo 1 termina siendo
una aplicación real, con una API HTTP, persistencia en base de datos, pruebas automatizadas, y un
proceso de despliegue reproducible.
Errores Comunes y Consejos
- Escribir la cadena de conexión de producción directamente en
appsettings.Production.json: ese fichero suele versionarse junto con el resto del código fuente; cualquier dato sensible de producción debe llegar por variable de entorno o por un gestor de secretos, nunca escrito ahí. - Publicar en modo
Debugen vez deRelease: pierde las optimizaciones del compilador y publica información adicional pensada solo para depurar en desarrollo (Lección 4), no para ejecutarse en producción. - Usar una única etapa de Docker con el SDK completo para la imagen final: funciona, pero genera una imagen mucho más pesada de lo necesario, con herramientas de compilación que ya no se usan una vez publicada la aplicación; la compilación multietapa del apartado 2 resuelve esto sin esfuerzo extra.
- Consejo: antes de dar por completado el despliegue, confirma explícitamente los tres puntos
de esta lección por separado: que
dotnet publishgenera el artefacto correcto, que la imagen Docker arranca y responde en local condocker run, y que la configuración de producción no contiene ningún dato sensible escrito directamente en un fichero versionado.
Ejercicios
-
Añade al
Dockerfiledel apartado 2 una variable de entornoASPNETCORE_ENVIRONMENT=Productioncon la instrucciónENV, de forma que cualquier contenedor construido a partir de esta imagen arranque en modo producción por defecto, salvo que se indique lo contrario al ejecutardocker run. -
Describe, en tus propias palabras y sin necesidad de configurar ninguna plataforma real, los cuatro pasos del pipeline de CI/CD del apartado 5 aplicados a un cambio concreto: añadir el endpoint
GET /socios/{id}que escribiste en el ejercicio 1 de la lección anterior.
Soluciones
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
ENV ASPNETCORE_ENVIRONMENT=Production
COPY --from=build /publicado .
EXPOSE 8080
ENTRYPOINT ["dotnet", "BiblioTech.Api.dll"]
(1) Build: al hacer git push con el nuevo endpoint, el pipeline ejecuta dotnet build
sobre toda la solución, confirmando que compila sin errores. (2) Test: ejecuta
dotnet test, incluyendo tanto las pruebas unitarias del Módulo 8 como las pruebas de
integración de la Lección 4 —si el endpoint nuevo no tuviera ninguna prueba de integración
propia, sería buena práctica añadir una antes de este paso. (3) Todo en verde: si algún
test falla, el pipeline se detiene aquí y el cambio no avanza. (4) Publish y despliegue: solo
si el paso anterior fue exitoso, se ejecuta dotnet publish y docker build para generar la
nueva imagen, que después se despliega según la plataforma elegida.
Conclusión
Este es el final del curso de Programación en C#. El recorrido empezó en el Módulo 1 con la
instalación del entorno de desarrollo y un primer programa que escribía "Hola Mundo" por consola;
continuó por las estructuras de control y el manejo de excepciones del Módulo 2; construyó, en el
Módulo 3, el primer modelo de objetos real de BiblioTech —MaterialBibliotecario, Libro,
Revista, Socio, Prestamo— con clases, herencia, polimorfismo y encapsulamiento; lo amplió en
el Módulo 4 con interfaces, genéricos, colecciones, LINQ y programación asíncrona; le dio memoria
persistente en el Módulo 5, con cuatro mecanismos de persistencia distintos y una primera conexión
a servicios externos; exploró, en el Módulo 6, las capacidades más internas de .NET —reflexión,
atributos, gestión de memoria y concurrencia—; le puso cara, en el Módulo 7, con cinco tecnologías
de interfaz distintas, de escritorio, web y móvil; y maduró su diseño en el Módulo 8, con
estándares de codificación, patrones de diseño, inyección de dependencias, pruebas unitarias y
refactorización disciplinada.
Este último módulo no ha hecho más que reunir todo ese camino en un único sistema completo: unos requisitos claros, una arquitectura elegida con criterio, una API que expone el dominio construido durante nueve módulos, una batería de pruebas que respalda cada cambio, y un proceso de despliegue reproducible que lleva ese código, de forma fiable, hasta donde puede usarlo alguien de verdad.
De "Hola Mundo" a una aplicación desplegada en producción: ese es el trayecto completo de este curso, y es también, en esencia, el trayecto de cualquier proyecto real de software. Lo que sostiene ese trayecto no es haber memorizado la sintaxis de C#, sino haber practicado, una y otra vez sobre el mismo dominio, a tomar decisiones de diseño, a desacoplar responsabilidades, a confiar en las pruebas antes que en la suerte, y a pensar en quien usará el código tanto como en quien lo escribe. Esas son las herramientas que seguirán siendo útiles mucho después de que cualquier detalle concreto de sintaxis quede obsoleto. Enhorabuena por haber llegado hasta aquí: BiblioTech ha quedado terminada, pero lo aprendido para construirla no tiene por qué detenerse ahora. El siguiente proyecto en C# ya depende solo de ti.
Curso de Programación en C#
Módulo 1: Introducción a C#
- Introducción a C#
- Configuración del Entorno de Desarrollo
- Programa Hola Mundo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Arrays y Cadenas de Texto
Módulo 2: Estructuras de Control
Módulo 3: Programación Orientada a Objetos
- Clases y Objetos
- Métodos
- Constructores y Destructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- Structs y Records: Tipos por Valor y por Referencia
Módulo 4: Conceptos Avanzados de C#
- Interfaces
- Delegados y Eventos
- Pattern Matching y Características Modernas de C#
- Genéricos
- Colecciones
- LINQ (Consulta Integrada en el Lenguaje)
- Programación Asíncrona
Módulo 5: Trabajando con Datos
- Entrada/Salida de Archivos
- Serialización
- Conectividad con Bases de Datos
- Entity Framework
- Trabajo con JSON y Consumo de APIs REST
Módulo 6: Temas Avanzados
- Reflexión
- Atributos
- Programación Dinámica
- Gestión de Memoria y Recolección de Basura
- Multihilo y Programación Paralela
Módulo 7: Construcción de Aplicaciones
- Formularios de Windows
- WPF (Windows Presentation Foundation)
- ASP.NET Core
- Blazor
- Xamarin y .NET MAUI
Módulo 8: Mejores Prácticas y Patrones de Diseño
- Estándares de Codificación y Mejores Prácticas
- Patrones de Diseño
- Inyección de Dependencias e Inversión de Control
- Pruebas Unitarias
- Revisión y Refactorización de Código
