Desarrollo de Docker profunda inmersión: de lo básico a la avanzada .NET Containerization (Español (Spanish))

Desarrollo de Docker profunda inmersión: de lo básico a la avanzada .NET Containerization

Sunday, 09 November 2025

//

37 minute read

Introducción

Docker ha transformado fundamentalmente cómo construimos, enviamos y ejecutamos aplicaciones.

Lo que comenzó como una herramienta de contenedorización simple ha evolucionado hasta convertirse en un ecosistema completo para el desarrollo y el despliegue de aplicaciones modernas.

  1. **Este es el cuarto y más completo artículo de mi serie Docker para desarrolladores .NET.**Si eres nuevo en Docker, es posible que quieras empezar con:
  2. Docker Composite- Primeros pasos con configuraciones básicas multicontenedor (julio 2024)
  3. Dependencias de desarrollo con Docker Compose- Creación de entornos locales de desarrollo (agosto 2024)

ImageSharp con Docker

  • **- Resolver problemas de permisos de volumen (agosto 2024)**En esta inmersión profunda, construiremos sobre esos cimientos para explorar:
  • Compuesta Docker lista para la producción: La pila real que ejecuta este blog
  • Autoacogida con recursos limitados: Técnicas prácticas de optimización para despliegues de VPS de presupuesto
  • Apoyo a la GPU: Ejecución de cargas de trabajo ML en contenedores
  • Multi-arquitectura construye: Apoyo ARM64 y AMD64
  • .NET 9 características del contenedor: Publicación de contenedores incorporados sin archivos Docker

.NET Aspire

: El enfoque .NET moderno para la orquestación de contenedores

NOTA: Esto es parte de mis experimentos con IA / una manera de gastar $1000 créditos Web Código Claude.

He alimentado esto un montón de papeles, mi comprensión, preguntas que tuve que generar este artículo.

Es divertido y llena un hueco que no he visto en ningún otro lugar.

Ya sea que esté implementando una sencilla aplicación web o orquestando una compleja arquitectura de microservicios con modelos de aprendizaje automático acelerados por GPU, esta guía le lleva desde lo básico de Docker a aplicaciones en contenedores listas para la producción, con ejemplos reales de ejecutar mayoritariamentelucid.com.

  • Fundamentos de Docker: Comprender los contenedores¿Qué es Docker, en serio?
  • **En su núcleo, Docker es una plataforma de contenedorización que empaqueta su aplicación y todas sus dependencias en una unidad estandarizada llamada contenedor.**A diferencia de las máquinas virtuales que virtualizan sistemas operativos completos, los contenedores comparten el núcleo del sistema operativo host mientras mantienen espacios de usuario aislados.

Piénsalo de esta manera:

# The classic developer problem
"It works on my machine!" 

# The container solution
"Ship your machine!" 

Máquinas virtuales

  1. : Cada VM ejecuta una pila completa del sistema operativo (núcleo Linux, bibliotecas del sistema, etc.) - pesado y lento para iniciarContenedores
  2. : Compartir el kernel del host, el código de la aplicación sólo del paquete y las dependencias - ligero y rápidoPor qué los contenedores son importantes para los desarrolladores
  3. **Los contenedores resuelven varios problemas críticos:**Coherencia en materia de medio ambiente
  4. : Los entornos de desarrollo, pruebas y producción son idénticosAislamiento de la dependencia
  5. : No más "DLL hell" o versiones de la biblioteca en conflictoConstrucciones reproducibles

: Misma entrada = misma salida, cada vez

Despliegue rápido

# An image is a template (like a class in OOP)
docker pull mcr.microsoft.com/dotnet/aspnet:9.0

# A container is a running instance (like an object)
docker run -d -p 8080:8080 myapp:latest

: Iniciar contenedores en segundos, no minutosEficiencia en el uso de los recursos

FROM mcr.microsoft.com/dotnet/aspnet:9.0      # Layer 1: Base OS + .NET runtime
WORKDIR /app                                   # Layer 2: Directory structure
COPY *.dll ./                                  # Layer 3: Application files
ENTRYPOINT ["dotnet", "MyApp.dll"]            # Layer 4: Startup command

: Ejecutar docenas de contenedores en un solo anfitrión

  • Conceptos esenciales de DockerImágenes frente a contenedores
  • Imágenesson sistemas de archivos inmutables y en capas.
  • **Cada instrucción en un Dockerfile crea una nueva capa:**Esta capa es poderosa:

Caché

: Las capas inalteradas se reutilizan, acelerando las construccionesCompartir

: Múltiples imágenes pueden compartir capas base

Your Machine (Windows/Mac/Linux)
    ↓ (reads Dockerfile)
Build Image (usually Linux)
    ↓ (executes RUN commands here)
Output Image (contains results)

Eficiencia

# You're on Windows, writing this Dockerfile
FROM ubuntu:24.04

# This RUN command executes in Ubuntu, NOT on your Windows machine!
RUN apt-get update && apt-get install -y curl

# This copies FROM your Windows filesystem
COPY myapp.exe /app/

# This executes IN the Ubuntu container
RUN chmod +x /app/myapp.exe

: Sólo es necesario descargar/cargar capas cambiadas

  1. **Entender la ejecución Dockerfile: ¡No su máquina!**Una fuente común de confusión para los principiantes de Docker:COPYLas órdenes en un Dockerfile no se ejecutan en su máquina - se ejecutan dentro del sistema operativo del contenedor de construcción.ADDEsto es lo que realmente sucede:
  2. **Por qué esto importa:**Perspectivas clave:RUNSistema de archivos local
  3. : Tuy
  4. comandos leídos desde su máquinaConstruir imagen

: Tuapt-getlos comandos se ejecutan en el sistema operativo del contenedor (no en su máquina)

Imagen de salida

FROM mcr.microsoft.com/dotnet/sdk:9.0  # This is Linux-based

# You might think: "But I'm on Windows, how can I use these Linux commands?"
RUN apt-get update  # ← Executes in the Linux build container, not your Windows machine
RUN dotnet restore  # ← Executes in the Linux build container

: La imagen final contiene todas las capas creadas durante la construcción

Multiplataforma

: Puede estar en Windows, construyendo una imagen de Linux, usando comandos Linux

# Multi-stage build: separates build environment from runtime
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src

# Copy only csproj files first (better layer caching)
COPY ["MyApp/MyApp.csproj", "MyApp/"]
COPY ["MyApp.Core/MyApp.Core.csproj", "MyApp.Core/"]

# Restore dependencies (cached unless csproj changes)
RUN dotnet restore "MyApp/MyApp.csproj"

# Copy everything else
COPY . .

# Build the application
WORKDIR "/src/MyApp"
RUN dotnet build "MyApp.csproj" -c Release -o /app/build

# Stage 2: Publish
FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish /p:UseAppHost=false

# Stage 3: Final runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
WORKDIR /app

# Create non-root user for security
RUN addgroup --gid 1001 appuser && \
    adduser --uid 1001 --gid 1001 --disabled-password --gecos "" appuser

# Copy published output from publish stage
COPY --from=publish /app/publish .

# Switch to non-root user
USER appuser

# Expose port (documentation only, doesn't actually publish)
EXPOSE 8080

# Set environment variables
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

ENTRYPOINT ["dotnet", "MyApp.dll"]

Esta es la razón por la que puedes escribir

comandos en un Dockerfile en Windows - no se están ejecutando en Windows!

graph TB
    subgraph "Your Machine"
        A[Source Code<br/>*.cs, *.csproj]
        B[Frontend Assets<br/>*.js, *.css]
        C[Configuration<br/>appsettings.json]
        D[Static Files<br/>wwwroot/]
    end

    subgraph "Stage 1: Build Container (SDK Image)"
        E[dotnet/sdk:9.0<br/>~1.5GB]
        F[Copy .csproj files]
        G[dotnet restore<br/>Download NuGet packages]
        H[Copy source code]
        I[dotnet build<br/>Compile to DLLs]
        J[Build artifacts<br/>/app/build/]
    end

    subgraph "Stage 2: Publish Container"
        K[dotnet publish<br/>Optimize & trim]
        L[Published output<br/>/app/publish/]
    end

    subgraph "Stage 3: Final Runtime Container (ASPNET Image)"
        M[dotnet/aspnet:9.0<br/>~220MB]
        N[Create app user<br/>Security]
        O[Copy published files<br/>ONLY production artifacts]
        P[Final image<br/>~250MB total]
    end

    subgraph "Frontend Build Pipeline (Parallel)"
        Q[npm install<br/>node_modules/]
        R[Webpack bundling<br/>*.js → dist/]
        S[TailwindCSS + PostCSS<br/>*.css → dist/]
        T[Optimized assets<br/>wwwroot/js/dist/<br/>wwwroot/css/dist/]
    end

    A --> F
    A --> H
    F --> G
    G --> H
    H --> I
    I --> J
    J --> K
    K --> L

    B --> Q
    Q --> R
    Q --> S
    R --> T
    S --> T

    L --> O
    T --> O
    C --> O
    D --> O

    M --> N
    N --> O
    O --> P

Están corriendo dentro del contenedor de construcción basado en Linux.

  1. **Ejemplo de escenario de confusión:**El demonio Docker maneja la traducción entre su sistema de archivos local y el entorno de construcción en contenedores.
  2. Mejores prácticas de DockerfileAquí hay un archivo Docker .NET listo para la producción con comentarios:.csprojComprender el flujo de construcción
  3. **Esto es lo que realmente sucede durante una compilación Docker multi-etapa - de donde vienen los archivos y donde terminan:**Perspectivas clave de este flujo:npm run buildImagen SDK descartada
  4. : El contenedor SDK de 1.5GB se tira después de publicar - sólo ~50MB de salida compilada se mueve hacia adelante: appsettings.jsonCaché de capaswwwroot/: Copiando
  5. antes de código fuente significa que la restauración de dependencias está en caché a menos que las dependencias cambienActivos de frente
  6. : Construido por separado (a menudo a través de) y copiado en la imagen final

Archivos de configuración

  1. ycontenido copiado de su máquina, no construido
  2. Imagen final: Comienza desde la imagen mínima de tiempo de ejecución (~220MB) + tu app (~30MB) + activos (~5MB) = ~255MB total
  3. Sólo archivos de producción: Código fuente, obj/, bin/, node_modules/ nunca llegar a la imagen final
  4. **Principios fundamentales ilustrados:**Estructuras multietapas
  5. : Las etapas de construcción/edición separadas reducen dramáticamente el tamaño final de la imagenOptimización de capas

: Copiar archivos de dependencia antes del código fuente para un mejor almacenamiento en caché

# Build the image
docker build -t myapp:1.0.0 -t myapp:latest .

# Run with common options
docker run -d \
  --name myapp \
  -p 8080:8080 \
  -e ConnectionStrings__DefaultConnection="Server=db;Database=myapp" \
  -v /data/logs:/app/logs \
  --restart unless-stopped \
  myapp:latest

# View logs
docker logs -f myapp

# Execute commands inside running container
docker exec -it myapp /bin/bash

# Stop and remove
docker stop myapp
docker rm myapp

Seguridad

: Ejecutar como usuario no root

Controles sanitarios

: Los orquestadores de contenedores pueden monitorear la salud de las aplicaciones

  • Entorno explícito
  • : Establecer valores predeterminados de producción
  • Construcción y ejecución
  • Docker Composite: Orquestración multicontenedor
  • Docker Compose le permite definir y ejecutar aplicaciones multicontenedor.

En lugar de administrar contenedores individualmente, describe toda la pila de aplicaciones en un archivo YAML.docker run¿Por qué Docker Compose?

Considere una aplicación web .NET típica:

ASP.NET Aplicación web básica**Base de datos PostgreSQLdocker-compose.yml**Redis caché

services:
  # Main ASP.NET Core application
  mostlylucid:
    image: scottgal/mostlylucid:latest
    restart: always
    healthcheck:
      test: [ "CMD", "curl", "-f -K", "https://mostlylucid:7240/healthy" ]
      interval: 30s
      timeout: 10s
      retries: 5
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    env_file:
      - .env
    environment:
      - Auth__GoogleClientId=${AUTH_GOOGLECLIENTID}
      - Auth__GoogleClientSecret=${AUTH_GOOGLECLIENTSECRET}
      - Auth__AdminUserGoogleId=${AUTH_ADMINUSERGOOGLEID}
      - SmtpSettings__UserName=${SMTPSETTINGS_USERNAME}
      - SmtpSettings__Password=${SMTPSETTINGS_PASSWORD}
      - Analytics__UmamiPath=${ANALYTICS_UMAMIPATH}
      - Analytics__WebsiteId=${ANALYTICS_WEBSITEID}
      - ConnectionStrings__DefaultConnection=${POSTGRES_CONNECTIONSTRING}
      - TranslateService__ServiceIPs=${EASYNMT_IPS}
      - Serilog__WriteTo__0__Args__apiKey=${SEQ_API_KEY}
      - Markdown__MarkdownPath=${MARKDOWN_MARKDOWNPATH}
    volumes:
      - /mnt/imagecache:/app/wwwroot/cache
      - /mnt/logs:/app/logs
      - /mnt/markdown:/app/markdown
      - ./mostlylucid.pfx:/app/mostlylucid.pfx
      - /mnt/articleimages:/app/wwwroot/articleimages
      - /mnt/mostlylucid/uploads:/app/wwwroot/uploads
    networks:
      - app_network
    depends_on:
      - db

  # PostgreSQL database
  db:
    image: postgres:16-alpine
    ports:
      - 5266:5432  # Custom external port to avoid conflicts
    env_file:
      - .env
    networks:
      - app_network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - /mnt/umami/postgres:/var/lib/postgresql/data
    restart: always

  # Cloudflare tunnel for secure external access
  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token ${CLOUDFLARED_TOKEN}
    env_file:
      - .env
    restart: always
    networks:
      - app_network

  # Umami analytics
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    env_file: .env
    environment:
      DATABASE_URL: ${DATABASE_URL}
      DATABASE_TYPE: ${DATABASE_TYPE}
      HASH_SALT: ${HASH_SALT}
      APP_SECRET: ${APP_SECRET}
      TRACKER_SCRIPT_NAME: getinfo
      API_COLLECT_ENDPOINT: all
    depends_on:
      - db
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    networks:
      - app_network
    restart: always

  # Translation service (CPU-limited for resource management)
  easynmt:
    image: easynmt/api:2.0.2-cpu
    volumes:
      - /mnt/easynmt:/cache/
    deploy:
      resources:
        limits:
          cpus: "4.0"  # Prevent translation service from consuming all CPU
    networks:
      - app_network

  # Caddy reverse proxy with automatic HTTPS
  caddy:
    image: caddy:latest
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - app_network
    restart: always

  # Seq centralized logging
  seq:
    image: datalust/seq
    container_name: seq
    restart: unless-stopped
    environment:
      ACCEPT_EULA: "Y"
      SEQ_FIRSTRUN_ADMINPASSWORDHASH: ${SEQ_DEFAULT_HASH}
    volumes:
      - /mnt/seq:/data
    networks:
      - app_network

  # Prometheus metrics collection
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - prometheus-data:/prometheus
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    networks:
      - app_network

  # Grafana visualization
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    volumes:
      - grafana-data:/var/lib/grafana
    networks:
      - app_network
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}

  # Host metrics exporter
  node_exporter:
    image: quay.io/prometheus/node-exporter:latest
    container_name: node_exporter
    command:
      - '--path.rootfs=/host'
    networks:
      - app_network
    restart: unless-stopped
    volumes:
      - '/:/host:ro,rslave'

  # Automatic container updates
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_LABEL_ENABLE=true
    command: --interval 300  # Check every 5 minutes

volumes:
  grafana-data:
  caddy_data:
  caddy_config:
  prometheus-data:

networks:
  app_network:
    driver: bridge

Seq para el registro

  1. Tal vez un servicio de trabajadores de fondoGestión de estos individualmente conapp_networklas órdenes se vuelven difíciles de manejar.
  2. **Docker Compose resuelve esto.**Ejemplo completo en el mundo real: Plataforma Blog de producción/mnt/*Aquí está el
  3. Producción efectivaque ejecuta este mismo sitio (principalmentelucid.com):
  4. **Patrones de producción clave:**Red única
  5. : Todos los servicios enpara simplificar
  6. Montajes de encuadernación: Rutas de acogida (
  7. ) para los datos persistentes que necesita para copia de seguridad / accesoVolúmenes nombrados.env: Almacenamiento administrado por Docker para datos que no necesita acceso directo a

Etiquetas de la Atalaya

: Sólo los servicios de actualización etiquetados explícitamente para auto-actualización

services:
  web:
    depends_on:
      db:
        condition: service_healthy  # Wait for health check
      redis:
        condition: service_started  # Just wait for start

Límites de recursoscondition: service_healthy: Las limitaciones de CPU en el servicio de traducción previenen el hambre de recursos

Mapeo externo de puertos

# .env file (never commit to git!)
DB_PASSWORD=super_secret_password
SMTP_PASSWORD=another_secret
services:
  web:
    environment:
      - DB_PASSWORD=${DB_PASSWORD}  # From .env file
      - STATIC_VALUE=production     # Hardcoded
    env_file:
      - .env                        # Load entire file

: PostgreSQL en 5266 en lugar de 5432 para evitar conflictos con otras instancias

Archivos de entorno

services:
  db:
    volumes:
      # Named volume (managed by Docker)
      - postgres_data:/var/lib/postgresql/data

  web:
    volumes:
      # Bind mount (maps host directory to container)
      - ./data/markdown:/app/Markdown
      - ./logs:/app/logs

: Secretos en:

  • archivo (nunca comprometido con git)
  • Docker Componer características clave
  • Dependencias de servicios
  • Docker Compone orquesta el orden de inicio.

Los:

  • requiere que el chequeo de salud de la base de datos pase antes de iniciar la aplicación web.
  • Variables y secretos del entorno
  • Para secretos de producción, utilice Docker Secrets o administradores secretos externos (Administrador de secretos AWS, Azure Key Vault, HashiCorp Vault).

Volúmenes designados vs Montajes encuadernados

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

services:
  web:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend  # Not exposed to frontend

Volúmenes designados

Administrado por Docker

services:
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

Persista los datos a través de los reinicios del contenedor

  • Se puede realizar una copia de seguridad/restaurar con comandos Docker
  • Compatible con multiplataforma
  • Montajes de encuadernación

Mapeo directo al sistema de archivos host

# Start all services (detached)
docker-compose up -d

# Start specific services
docker-compose up -d web db

# View logs (all services)
docker-compose logs -f

# View logs (specific service)
docker-compose logs -f web

# Stop services (containers remain)
docker-compose stop

# Stop and remove containers
docker-compose down

# Stop, remove containers, and remove volumes
docker-compose down -v

# Rebuild and restart
docker-compose up -d --build

# Scale a service
docker-compose up -d --scale worker=3

# Execute command in running service
docker-compose exec web /bin/bash

# Run one-off command
docker-compose run --rm web dotnet ef database update

Útil para el desarrollo (recarga de código en vivo)

Archivos de configuración, registros, cargas

Establecimiento de redesLas redes proporcionan aislamiento.

services:
  web:
    build: .
    environment:
      - ASPNETCORE_ENVIRONMENT=Development

**Aquí, la base de datos sólo es accesible a los servicios de backend, no directamente expuestos.**Controles de salud

services:
  web:
    volumes:
      - .:/app  # Live code reloading
    ports:
      - "5000:8080"

**Los controles médicos permiten a Docker:**Determinar si un contenedor está realmente listo (no acaba de empezar)

services:
  web:
    image: registry.example.com/myapp:${VERSION}
    restart: always
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '2'
          memory: 2G
# Development (base + override)
docker-compose up -d

# Production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Reiniciar contenedores poco saludables

Proporcionar estatus a los orquestadores (Kubernetes, Docker Swarm)

Compositores de Docker comunes

# Install NVIDIA Container Toolkit (Ubuntu/Debian)
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

# Configure Docker daemon
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# Test GPU access
docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu24.04 nvidia-smi

Desarrollo vs Producción Componer Archivos

# Use NVIDIA CUDA base image
FROM nvidia/cuda:12.6.0-cudnn-runtime-ubuntu24.04

# Install Python
RUN apt-get update && apt-get install -y \
    python3.12 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Install PyTorch with CUDA support
COPY requirements.txt .
RUN pip3 install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124

# Copy application
COPY . .

# Test GPU on container start
RUN python3 -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}'); print(f'GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"None\"}')"

ENTRYPOINT ["python3", "train.py"]

Preocupaciones separadas con múltiples archivos de composición:

# Run with all GPUs
docker run --gpus all myapp:gpu

# Run with specific GPUs
docker run --gpus '"device=0,2"' myapp:gpu

# Run with GPU memory limits
docker run --gpus all --memory=16g myapp:gpu

docker-compose.yml

services:
  ml-trainer:
    build:
      context: .
      dockerfile: Dockerfile.gpu
    image: myapp:gpu
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all  # or specific count: 1, 2, etc.
              capabilities: [gpu]
    volumes:
      - ./models:/app/models
      - ./data:/app/data
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      - CUDA_VISIBLE_DEVICES=0,1  # Use GPUs 0 and 1

(base):

docker-compose.override.yml(desarrollo - auto-fusionado):docker-compose.prod.yml

(producción):

  • Soporte GPU en Docker: aceleración de cargas de trabajo MLEl aprendizaje automático, la computación científica y las aplicaciones de procesamiento de vídeo a menudo requieren aceleración de la GPU.
  • **Docker soporta GPUs NVIDIA a través del kit de herramientas NVIDIA Container.**Configuración de NVIDIA Container Toolkit
  • Dockerfile para la aplicación Python/PyTorch acelerada por GPUContenedores GPU en funcionamiento
  • GPU en Docker ComposeEjemplo del mundo real: Servicio de traducción con GPU y CPU

Aquí hay un ejemplo de producción real de

services:
  translation:
    image: scottgal/mostlylucid-nmt:gpu
    container_name: translation-gpu
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    environment:
      - MODEL_FAMILY=opus-mt
      - FALLBACK_MODELS=mbart50,m2m100
      - CUDA_VISIBLE_DEVICES=0
      - LOG_LEVEL=info
    volumes:
      - model_cache:/app/cache  # Persistent model storage
    ports:
      - "8888:8888"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8888/health"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  model_cache:

principalmente lucid-nmt

, un servicio de traducción automática neuronal que construí que permite la auto-traducción en este blog.

services:
  translation:
    image: scottgal/mostlylucid-nmt:cpu
    container_name: translation-cpu
    environment:
      - MODEL_FAMILY=opus-mt
      - FALLBACK_MODELS=mbart50,m2m100
    volumes:
      - model_cache:/app/cache
    ports:
      - "8888:8888"
    restart: unless-stopped

volumes:
  model_cache:

El proyecto demuestra:

  • scottgal/mostlylucid-nmt:gpuVariantes de GPU y CPU
  • scottgal/mostlylucid-nmt:cpu- Misma base de código, diferentes imágenes de base
  • scottgal/mostlylucid-nmt:gpu-minMulti-arquitectura construye
  • scottgal/mostlylucid-nmt:cpu-min- Soporta AMD64 y ARM64

Imágenes Docker optimizadas

  • - Variantes tanto completas como mínimasPreparados para la producción
  • - Controles de salud, persistencia del volumen, tala adecuadaServicio de Traducción acelerado por la GPU
  • Alternativas solo para CPUPara entornos sin GPUs, el mismo servicio se ejecuta en la CPU:
  • Variantes de imagen disponibles:- CUDA 12.6 con soporte GPU PyTorch (~5GB)
  • - Sólo CPU, menor huella (~2.5GB): /health- Construcción mínima de GPU, sin modelos precargados (~4GB)/ready- Creación mínima de CPU (~1,5 GB)
  • **Características principales:**Aceleración de la GPU

: 10-15x traducción más rápida con CUDAAuto-Descarga del modelo: Descargas de modelos de traducción bajo demanda

Soporte de retroceso

: Tries Opus-MT → mBART50 → M2M100 para la cobertura máxima del lenguaje

Persistencia del volumen

# Problem: Image built on M1 Mac won't run on Linux server
docker build -t myapp:latest .  # Builds for ARM64
docker push myapp:latest
# Server tries to run it... error: "exec format error"

# Solution: Build for multiple platforms
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .

: Modelos caché a través de contenedores reinicia

Puntos finales en materia de salud

# Verify buildx is available
docker buildx version

# Create a new builder instance
docker buildx create --name multiarch --driver docker-container --use

# Inspect and bootstrap the builder
docker buildx inspect --bootstrap

# List available platforms
docker buildx inspect | grep Platforms

y

para orquestadores

# Use official multi-arch base images
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base

# For platform-specific operations, use build arguments
ARG TARGETPLATFORM
ARG BUILDPLATFORM

RUN echo "Building on $BUILDPLATFORM for $TARGETPLATFORM"

# Install architecture-specific dependencies
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
        apt-get update && apt-get install -y some-arm64-package; \
    elif [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
        apt-get update && apt-get install -y some-amd64-package; \
    fi

Multiarquitectura

# Build and push for AMD64 and ARM64
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myregistry/myapp:latest \
  -t myregistry/myapp:1.0.0 \
  --push \
  .

# Build without pushing (loads into local Docker)
# Note: Can only load one platform at a time
docker buildx build \
  --platform linux/amd64 \
  -t myapp:latest \
  --load \
  .

# Build and export to tar files
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp:latest \
  -o type=tar,dest=./myapp.tar \
  .

: Se ejecuta en x86_64 y ARM64 (Apple Silicon, Raspberry Pi)

Ver la

proyecto completo en GitHub

# Build multi-arch images first
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .

# Then use in compose
services:
  web:
    image: myapp:latest  # Already built for multiple architectures

para ejemplos de Dockerfile, scripts de compilación y documentación de API.

#!/bin/bash
# build-multiarch.sh

docker buildx build --platform linux/amd64,linux/arm64 \
  -t myregistry/web:latest \
  -f web/Dockerfile \
  --push \
  web/

docker buildx build --platform linux/amd64,linux/arm64 \
  -t myregistry/worker:latest \
  -f worker/Dockerfile \
  --push \
  worker/

docker-compose pull  # Pull the multi-arch images
docker-compose up -d

Multi-Arquitectura construye con Docker Buildx

Las aplicaciones modernas necesitan ejecutarse en múltiples arquitecturas: x86_64 (AMD64) para servidores, ARM64 para Raspberry Pi y Apple Silicon Macs, a veces incluso ARM32 para dispositivos integrados.

name: Build and Push Multi-Arch Images

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: myregistry/myapp
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix={{branch}}-

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=registry,ref=myregistry/myapp:buildcache
          cache-to: type=registry,ref=myregistry/myapp:buildcache,mode=max

Por qué es importante la multiarquitectura

  • Configuración de Buildx
  • Docker Buildx está incluido en Docker Desktop.
  • Para Linux:
  • Archivo Docker multiarchitectura
  • La mayoría de los Dockerfiles funcionan sin cambios, pero aquí hay algunos consejos:

Construcción para múltiples plataformas

Multi-arquitectura con Docker Compose

Desafortunadamente, Docker Compose no soporta directamente buildx.

Soluciones:

# Publish as a container image (no Dockerfile needed!)
dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer

# Specify image name and tag
dotnet publish \
  --os linux \
  --arch x64 \
  -p:PublishProfile=DefaultContainer \
  -p:ContainerImageName=myapp \
  -p:ContainerImageTag=1.0.0

# Multi-architecture
dotnet publish --os linux --arch arm64 -p:PublishProfile=DefaultContainer

Opción 1: Preconstruir imágenes

Opción 2: Construir script.csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>

    <!-- Container Configuration -->
    <ContainerImageName>myapp</ContainerImageName>
    <ContainerImageTag>$(Version)</ContainerImageTag>
    <ContainerRegistry>myregistry.azurecr.io</ContainerRegistry>

    <!-- Base image (defaults to mcr.microsoft.com/dotnet/aspnet:9.0) -->
    <ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:9.0-alpine</ContainerBaseImage>

    <!-- Container runtime configuration -->
    <ContainerWorkingDirectory>/app</ContainerWorkingDirectory>
    <ContainerPort>8080</ContainerPort>
    <ContainerEnvironmentVariable Include="ASPNETCORE_ENVIRONMENT">Production</ContainerEnvironmentVariable>

    <!-- User (security best practice) -->
    <ContainerUser>app</ContainerUser>

    <!-- Labels -->
    <ContainerLabel Include="org.opencontainers.image.description">My awesome app</ContainerLabel>
    <ContainerLabel Include="org.opencontainers.image. automatically configures these based on AppHost
builder.AddServiceDefaults();
builder.AddRedisClient("cache");
builder.AddNpgsqlDbContext<MyDbContext>("mydb");

var app = builder.Build();
app.MapDefaultEndpoints();  // Health, metrics, etc.

Ejemplo del mundo real: oleoducto CI/CD

dotnet run --project MyDistributedApp.AppHost

El flujo de trabajo de GitHub Actions para multi-arquitectura construye:

  • Este flujo de trabajo:
  • Activadores en empujes a las etiquetas principales o de versión
  • Establece QEMU para emulación multiplataforma
  • Construye para AMD64 y ARM64

Genera etiquetas automáticamente (nombre de rama, versiones semánticas, SHA)

Utiliza almacenamiento en caché de registro para construcciones más rápidas

  • .NET 9 Mejoras en contenedores
  • .NET 9 introduce mejoras significativas en el soporte de contenedores, lo que hace más fácil que nunca para contenedorizar aplicaciones .NET sin siquiera escribir un Dockerfile.
  • Publicación integrada de contenedores
  • Con .NET 9, puede publicar una aplicación en contenedores directamente:

Configuración de las propiedades del contenedor

  • Añadir a su
  • Ejecutar todo:
  • Aspire lanza:
  • Dashboard en http://localhost:15888

Todos los servicios con la configuración adecuadaRedis y PostgreSQL en contenedores

Rastreo distribuido entre los servicios

Aspire vs Docker Compose

# Generate Docker Compose
dotnet run --project MyDistributedApp.AppHost -- \
  --publisher compose \
  --output-path ../deploy

# Generate Kubernetes manifests
dotnet run --project MyDistributedApp.AppHost -- \
  --publisher manifest \
  --output-path ../deploy/k8s

Docker Compose:

# Docker Compose
docker-compose -f deploy/docker-compose.yml up -d

# Kubernetes
kubectl apply -f deploy/k8s/

Lenguaje agnóstico

Centrada en la infraestructura

// Add various backing services
var redis = builder.AddRedis("cache");
var postgres = builder.AddPostgres("db").AddDatabase("mydb");
var rabbitmq = builder.AddRabbitMQ("messaging");
var mongodb = builder.AddMongoDB("mongo").AddDatabase("docs");
var sql = builder.AddSqlServer("sql").AddDatabase("business");

// Add Azure services
var storage = builder.AddAzureStorage("storage");
var cosmos = builder.AddAzureCosmosDB("cosmos");
var servicebus = builder.AddAzureServiceBus("messaging");

// Use in services
builder.AddProject<Projects.MyService>("service")
       .WithReference(redis)
       .WithReference(postgres)
       .WithReference(rabbitmq);

Descubrimiento manual de servicios

Trae tu propia observabilidad.

Aspire:

.NET-specificCentrada en el desarrolloDescubrimiento automático del servicio

services:
  smtp4dev:
    image: rnwood/smtp4dev
    ports:
      - "3002:80"
      - "2525:25"
    volumes:
      - e:/smtp4dev-data:/smtp4dev
    restart: always

  postgres:
    image: postgres:16-alpine
    container_name: postgres
    ports:
      - "5432:5432"
    env_file:
      - .env
    volumes:
      - e:/data:/var/lib/postgresql/data
    restart: always

Telemetría incorporada

  • Genera manifiestos de despliegueUsar ambos
  • **: Aspire para el desarrollo local, genera Docker Compose/Kubernetes para la producción.**Implementando aplicaciones Aspire
  • **Generar manifiestos de despliegue:**A continuación, despliegue:

Componentes AspireLas integraciones preconstruidas hacen que añadir servicios sea trivial:Auto-acogida en recursos limitados: Optimización Práctica

La ejecución de una pila de observabilidad completa como la anterior requiere recursos significativos.

Si te alojas en un VPS con 4 GB de RAM o un viejo portátil, aquí tienes estrategias prácticas para reducir el consumo de recursos mientras mantienes la funcionalidad.

services:
  # Core application
  mostlylucid:
    image: scottgal/mostlylucid:latest
    restart: always
    env_file: .env
    volumes:
      - ./markdown:/app/markdown
      - ./logs:/app/logs
    networks:
      - app_network
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
        reservations:
          memory: 256M

  # Database only
  db:
    image: postgres:16-alpine
    env_file: .env
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - app_network
    deploy:
      resources:
        limits:
          memory: 512M
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 30s
      timeout: 5s
      retries: 3

  # Caddy for HTTPS
  caddy:
    image: caddy:latest
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    networks:
      - app_network
    deploy:
      resources:
        limits:
          memory: 128M

volumes:
  db_data:
  caddy_data:

networks:
  app_network:

Dependencias de desarrollo únicamente

  • **Para el desarrollo local, usted no necesita la pila de producción completa.**Los
  • devdeps-docker-compose.ymlenfoque ejecuta sólo lo que necesita:docker logsPor qué esto funciona para el desarrollo:
  • SMTP4Dev: Prueba la funcionalidad de correo electrónico sin un servidor SMTP real
  • PostgreSQL: Base de datos de producción de coincidencias
  • Huella total: ~200MB RAM vs 2-4GB para la pila completa

Ver la

Guía de dependencias de desarrollo completo

para las instrucciones de instalación.

# Just app + database + reverse proxy
docker-compose up -d mostlylucid db caddy

Configuración de la producción con conocimientos de recursos

# Add lightweight monitoring
docker-compose up -d mostlylucid db caddy seq
# Use Seq free 10GB/month

Para un presupuesto VPS (2-4GB RAM), priorizar los servicios esenciales:

# Add everything
docker-compose up -d

Lo que se elimina y alternativas:

Sin Prometeo/Grafana

services:
  myapp:
    image: postgres:16-alpine     # 50% smaller than postgres:16
    # vs
    image: postgres:16            # Full Debian base

**: Utilizar monitoreo externo (UptimeRobot, BetterStack grabe libre)**No Seq

: Usar el registro basado en archivos +

, o nivel libre de Seq Cloud

db:
  image: postgres:16-alpine
  # One instance, multiple databases
  # Umami, Mostlylucid, etc. all share this PostgreSQL

No hay Watchtower: Actualizaciones manuales con las notificaciones de GitHub Actions

Sin servicio de traducción

easynmt:
  deploy:
    resources:
      limits:
        cpus: "2.0"      # Don't let translation consume all CPU
      reservations:
        cpus: "0.5"      # Guarantee minimum

: Ejecute bajo demanda en un contenedor separado que inicie/detenga manualmente

Límites de recursos

: Impedir que cualquier servicio único consuma toda la memoriaEstrategia de mejora progresivaComience mínimo, agregue servicios según sea necesario:

mostlylucid:
  volumes:
    - /mnt/imagecache:/app/wwwroot/cache  # ImageSharp cache persists across restarts

**Etapa 1: Base (512MB-1GB VPS)**Etapa 2: Añadir Observabilidad (2GB VPS)

Etapa 3: pila completa (4GB+ VPS)

Técnicas de optimización de recursos |---------|-----------------|---------------------| 1. Usar imágenes alpinas Ahorros

: Las imágenes alpinas son 50-70% más pequeñas2.

Instancia de base de datos compartida

En lugar de una base de datos por servicio, utilice una instancia de PostgreSQL con múltiples bases de datos:

# Create a separate compose file
# translation-compose.yml
services:
  easynmt:
    image: easynmt/api:2.0.2-cpu
    ports:
      - "8888:8888"
    volumes:
      - /mnt/easynmt:/cache/

# Only run when needed
docker-compose -f translation-compose.yml up -d

# Translate your content
# ...

# Shut down when done
docker-compose -f translation-compose.yml down

Ahorros: 400 MB de RAM por base de datos adicional que consolida

3.

Limite la CPU para los servicios de fondo

# Minimal production compose
services:
  mostlylucid:
    image: scottgal/mostlylucid:latest
    restart: always
    env_file: .env
    volumes:
      - /mnt/markdown:/app/markdown
      - /mnt/logs:/app/logs
      - /mnt/imagecache:/app/wwwroot/cache
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    env_file: .env
    volumes:
      - /mnt/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 30s

  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel run --token ${CLOUDFLARED_TOKEN}
    restart: always

  watchtower:
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_LABEL_ENABLE: "true"
    command: --interval 3600  # Check once per hour, not every 5 minutes

Esto evita que los trabajos de fondo de hambre su aplicación web.

  • Volúmenes persistentes de caché
  • Cubierto en:

ImageSharp con Docker

  • , montar directorios de caché evita el reprocesamiento innecesario:
  • ¿Por qué importa?docker logs: Sin esto, cada contenedor reinicia regenera todas las miniaturas/imágenes procesadas.
  • Utilizar Servicios Externos (Libres Tiers)
  • Servicio # # RAM auto-acogida # # alternativa externa

Seq ~500MB Seq Cloud (10GB/mes gratis)

Prometheus + Grafana ~600MB Nube de Grafana (nivel libre)

Umami ~200MB Plausible (pagado) o auto-anfitrión en otra parte

# Simple health check script
#!/bin/bash
while true; do
  curl -f http://localhost/healthz || echo "Health check failed!" | mail -s "Alert" [email protected]
  sleep 300
done

Estrategia

# Watch for errors
docker-compose logs -f --tail=100 | grep -i error

# Email on critical errors
docker-compose logs -f | grep -i "critical" | while read line; do
  echo "$line" | mail -s "Critical Error" [email protected]
done

: Descargue la observabilidad a niveles libres, mantenga la aplicación de núcleo en su VPS.

# Quick resource check
docker stats --no-stream

# Pretty output
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

6.

Servicios en régimen de depósito

Para servicios de uso infrecuente como traducción:

Ahorros

# Add Aspire to your solution
dotnet new aspire-apphost -n Mostlylucid.AppHost
cd Mostlylucid.AppHost

: 1-2GB RAM cuando el servicio de traducción no se está ejecutando

var builder = DistributedApplication.CreateBuilder(args);

// PostgreSQL with persistent data
var postgres = builder.AddPostgres("postgres")
    .WithDataVolume()  // Persistent storage
    .WithPgAdmin();     // Optional: PgAdmin for database management

var mostlylucidDb = postgres.AddDatabase("mostlylucid");
var umamiDb = postgres.AddDatabase("umami");

// Seq for centralized logging
var seq = builder.AddSeq("seq")
    .WithDataVolume();

// Redis for caching (if needed)
var redis = builder.AddRedis("cache")
    .WithDataVolume()
    .WithRedisCommander();  // Optional: Redis Commander UI

// Main blog application
var mostlylucid = builder.AddProject<Projects.Mostlylucid>("web")
    .WithReference(mostlylucidDb)
    .WithReference(seq)
    .WithReference(redis)
    .WithEnvironment("TranslateService__Enabled", "false")  // Disable for dev
    .WithHttpsEndpoint(port: 7240, name: "https");

// Umami analytics
var umami = builder.AddContainer("umami", "ghcr.io/umami-software/umami", "postgresql-latest")
    .WithReference(umamiDb)
    .WithEnvironment("DATABASE_TYPE", "postgresql")
    .WithEnvironment("TRACKER_SCRIPT_NAME", "getinfo")
    .WithEnvironment("API_COLLECT_ENDPOINT", "all")
    .WithHttpEndpoint(port: 3000, name: "http");

// Translation service (CPU version, with resource limits)
var translation = builder.AddContainer("easynmt", "easynmt/api", "2.0.2-cpu")
    .WithDataVolume("/cache")
    .WithHttpEndpoint(port: 8888, name: "http")
    .WithEnvironment("MODEL_FAMILY", "opus-mt");

// Scheduler service (Hangfire background jobs)
var scheduler = builder.AddProject<Projects.Mostlylucid_SchedulerService>("scheduler")
    .WithReference(mostlylucidDb)
    .WithReference(seq);

// Prometheus for metrics
var prometheus = builder.AddContainer("prometheus", "prom/prometheus", "latest")
    .WithDataVolume()
    .WithBindMount("./prometheus.yml", "/etc/prometheus/prometheus.yml")
    .WithHttpEndpoint(port: 9090);

// Grafana for visualization
var grafana = builder.AddContainer("grafana", "grafana/grafana", "latest")
    .WithDataVolume()
    .WithHttpEndpoint(port: 3001)
    .WithEnvironment("GF_SECURITY_ADMIN_PASSWORD", builder.Configuration["Grafana:AdminPassword"] ?? "admin");

builder.Build().Run();

Ejemplo de alojamiento de presupuesto en el mundo real

Esto es lo que funciona con un VPS Hetzner $6/mes (2 vCPU, 4GB RAM):**Utilización total de los recursos:**RAM: ~800MB (deja libre 3.2GB)

// Extensions.cs
public static class Extensions
{
    public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
    {
        // OpenTelemetry
        builder.Services.AddOpenTelemetry()
            .WithMetrics(metrics =>
            {
                metrics.AddAspNetCoreInstrumentation()
                    .AddHttpClientInstrumentation()
                    .AddRuntimeInstrumentation();
            })
            .WithTracing(tracing =>
            {
                if (builder.Environment.IsDevelopment())
                {
                    tracing.SetSampler(new AlwaysOnSampler());
                }

                tracing.AddAspNetCoreInstrumentation()
                    .AddHttpClientInstrumentation()
                    .AddEntityFrameworkCoreInstrumentation();
            });

        // Health checks
        builder.Services.AddHealthChecks()
            .AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "live" });

        return builder;
    }

    public static IApplicationBuilder MapDefaultEndpoints(this WebApplication app)
    {
        app.MapHealthChecks("/healthz");
        app.MapHealthChecks("/ready", new HealthCheckOptions
        {
            Predicate = check => check.Tags.Contains("ready")
        });

        return app;
    }
}

Disco: ~2GB

var builder = WebApplication.CreateBuilder(args);

// Add Aspire service defaults (telemetry, health checks)
builder.AddServiceDefaults();

// Add services
builder.AddNpgsqlDbContext<MostlylucidDbContext>("mostlylucid");
builder.AddRedisClient("cache");

// Existing service registrations...
// builder.Services.AddControllersWithViews();
// etc...

var app = builder.Build();

// Map Aspire default endpoints
app.MapDefaultEndpoints();

// Existing middleware...
app.Run();

CPU: <10% inactivo, <50% bajo carga

Lo que es diferente:

  1. No Prometheus/Grafana (usar UptimeRobot + Cloudflare Analytics): dotnet run --project Mostlylucid.AppHostNo Seq (utilización
  2. **+ grep ocasional)**No Umami (usar Cloudflare Web Analytics - gratis)
    • Watchtower comprueba cada hora en vez de cada 5 minutos
    • No hay servicio de traducción (ejecutar manualmente cuando sea necesario)
    • Seguimiento de un presupuesto
    • Sin Prometeo/Grafana, utilice estas alternativas gratuitas:
  3. **Vigilancia de la salud:**Monitorización de registros:
  4. **Uso de los recursos:**Versión Aspire: El camino .NET.envAhora vamos a reimaginar toda la pila usando .NET Aspire.

Esto le da todos los beneficios de orquestación con una mejor integración .NET y una experiencia de desarrollador increíble.

Configuración de Aspire para la mayoría de los lúcidos

# Generate Docker Compose
dotnet run --project Mostlylucid.AppHost -- \
  --publisher compose \
  --output-path ./deploy

# This creates a production-ready docker-compose.yml
cd deploy
docker-compose up -d

**En primer lugar, crear el Aspire App Host:**Principalmente lúcido.AppHost/Program.cs:

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data

  mostlylucid-db:
    image: postgres:16
    # Database initialization

  seq:
    image: datalust/seq:latest
    environment:
      ACCEPT_EULA: Y
    volumes:
      - seq-data:/data

  web:
    image: scottgal/mostlylucid:latest
    environment:
      ConnectionStrings__mostlylucid: Host=postgres;Database=mostlylucid;Username=postgres;Password=${POSTGRES_PASSWORD}
      ConnectionStrings__cache: cache:6379
    depends_on:
      - postgres
      - cache
      - seq

  cache:
    image: redis:7-alpine
    volumes:
      - redis-data:/data

  # ... other services

Predeterminados de servicio de Aspire

Crear |---------|---------------|-------------| | Mayormentelucid.ServiceDefaultsproyecto: | Actualizar la mayoría de lucid/Program.csBeneficios del enfoque Aspire | **Experiencia en materia de desarrollo:**Comando único | empieza todo. | docker-compose up | dotnet runTablero | **: Hermosa interfaz de usuario en http://localhost:15888 mostrando:**Todos los servicios en vivo | Registros de todos los servicios en un solo lugar | docker logsRastros distribuidos entre los servicios | Métricas y controles sanitariosDescubrimiento de servicio | : Los servicios se encuentran automáticamente a través de nombresConfiguración | : Centralizado en AppHost, no másmalabares

Despliegue de producción:

Generar manifiestos de despliegue de Aspire:

  • Generado docker-compose.yml
  • (simplificado):
  • Aspire vs Docker Composite tradicional
  • Característica de Docker Compose .NET Aspire
  • Configuración

Manual YAML C# código con IntelliSense

  • Descubrimiento de servicio
  • Manual env vars Automático
  • Observabilidad
  • Traiga su propio incorporado (OpenTelemetry)
  • Desarrollo

+ Tablero

  • Depuración
  • Adjuntar al contenedor F5 en Visual Studio
  • Registros

Tablero centralizado

Rastreo

  1. **Setup manualmente Rastreo automatico distribuido**Producción
  2. **Usar YAML directamente Generar manifiestos**Curva de aprendizaje
  3. **YAML sintaxis C# ya sabes**Cuándo usar Aspire vs Docker Compose
  4. **Usar Aspire cuando:**Edificio .NET microservicios
  5. Quiere depurar integradoEl equipo está cómodo con C#

Es necesario distribuir el rastreo fuera de la caja

  • Se está implementando en Azure Container Apps (soporte nativo)
  • Use Docker Compose cuando:
  • Servicios de políglotas (Node.js, Python, Go, etc.)
  • El equipo prefiere la infraestructura como código YAML
  • Desplegando a cualquier host compatible con Docker
  • Necesita el máximo control sobre la configuración del contenedor

Despliegues simples de un solo servicio

Usar ambos:

  1. Aspiración para el desarrollo local
  2. Generar Docker Compose para el despliegue de producción
  3. ¡Lo mejor de ambos mundos!
  4. Evolución de la configuración Docker de este bloglatest)
  5. El viaje Docker de este blog muestra la progresión típica:
  6. Julio 2024 - Inicio sencillo.dockerignore: Sólo la aplicación, Cloudflared, y Watchtower
  7. Agosto 2024 - Dependencias Dev
  8. : Servicios agregados únicamente para el desarrollo

Agosto 2024 - ImageSharp Fix

  1. : Permisos de volumen resueltos para el almacenamiento en caché
  2. Noviembre 2024 - Apilado completo
  3. : Observabilidad completa con Prometheus, Grafana, Seq.envHoy
  4. : Aspire opción para el desarrollo .NET-first
  5. Lecciones aprendidas:depends_onComience simple, agregue complejidad sólo cuando sea necesario
  6. Configuraciones separadas de dev y de producción
  7. Los límites de recursos impiden que un servicio mate a otros
  8. Los montajes de volumen para cachés ahorran tiempo de reprocesamiento significativo

Watchtower permite actualizaciones automáticas de tiempo cero

  1. Observabilidad vale el costo de los recursos en la producción
  2. Resumen de las mejores prácticas
  3. Mejores prácticas de Dockerfile
  4. Usar construcciones multietapas
  5. Ejecutar como usuario no root
  6. Instrucciones de pedido para el almacenamiento en caché óptimo
  7. Usar etiquetas de imagen base específicas (not)
  8. Incluir los controles de salud

Uso

  1. para excluir archivos innecesarios
  2. Minimizar capas (combinar comandos RUN)
  3. Utilizar argumentos de construcción para la flexibilidad
  4. Componer las mejores prácticas de Docker.dockerignoreUsar volúmenes nombrados para los datos
  5. Implementar controles de salud
  6. Uso
  7. para secretos (nunca cometer)
  8. Definir redes explícitas

Uso

con condiciones de salud

# Check logs
docker logs container-name

# Common issues:
# 1. Port already in use
docker ps | grep 8080  # Find conflicting container
docker stop conflicting-container

# 2. Missing environment variables
docker inspect container-name | grep Env

# 3. Failed health check
docker inspect container-name | grep Health -A 20

Establecer políticas de reinicio

# Enable BuildKit for faster builds
export DOCKER_BUILDKIT=1

# Use build cache
docker build --cache-from myapp:latest -t myapp:latest .

# Check what's taking time
docker build --progress=plain -t myapp:latest .

Configuración separada de dev/prod

# Containers can't communicate
# Solution: Ensure they're on the same network
docker network ls
docker network inspect network-name

# DNS not working
# Container names are DNS names within Docker networks
docker exec web ping db  # Should work if both on same network

Límites de los recursos en la producción

# Permission denied on volume
# Solution: Match user IDs
FROM ubuntu
RUN useradd -u 1000 appuser  # Match host user ID
USER appuser

Mejores prácticas en materia de seguridad

Ejecutar como usuario no root

Usar imágenes de base cinceladas/mínimas

Escanea imágenes para detectar vulnerabilidades

Uso

  • generosamente
  • Imágenes alpinas o cinceladas para tamaños más pequeños
  • Usar monturas de volumen para el desarrollo
  • Configurar los límites de recursos apropiados
  • Utilizar controles médicos para la orquestación

Solución de problemas comunes

  • El contenedor no comenzará
  • Compilaciones lentas
  • Cuestiones relativas a la creación de redes
  • Problemas con el permiso de volumen
  • Conclusión
  • Docker ha evolucionado de una sencilla herramienta de contenedorización a una plataforma integral para construir, enviar y ejecutar aplicaciones modernas.

Esta guía le ha llevado de los fundamentos a los despliegues listos para la producción, con ejemplos del mundo real de ejecutar mayoritariamentelucid.com.

  • Llaves para llevar
  • Para principiantes:
  • Comience con el
  • Configuración básica de Docker Compose
    • sólo 3 servicios

Uso

  • Dependencias únicamente relacionadas con el desarrollo
  • para el trabajo local
  • Resolver problemas comunes como
  • permisos de volumen

Temprano

Para autoaficionados en VPS de presupuesto:

Inicio mínimo: App + Base de datos + Proxy Inverso (~800MB RAM) |-------|----------|-----------|------------| | Usar imágenes alpinas y límites de recursosDescarga la observabilidad a niveles libres (Seq Cloud, Grafana Cloud, UptimeRobot) | Ejecutar servicios caros (traducción, ML) sólo a peticiónUn VPS de $6/mes puede ejecutar un blog de producción con espacio de sobra | **Para despliegues de producción:**Los chequeos de salud permiten actualizaciones de tiempo cero con Watchtower | **Las redes separadas proporcionan seguridad (aislamiento frontal/retroceso)**Montajes encuadernados para datos que necesita para hacer una copia de seguridad/acceso | Volúmenes designados para el almacenamiento administrado por DockerLos límites CPU/memoria previenen la inanición de recursos

El túnel Cloudflare elimina la necesidad de direcciones IP públicasPara los desarrolladores de .NET:

Publicación integrada de contenedores en .NET 9 elimina Dockerfiles para aplicaciones simples

Las imágenes de Ubuntu cinceladas proporcionan una superficie de ataque mínima

  • .NET Aspire ofrece la mejor experiencia de desarrollo localAspire puede generar Docker Compose para el despliegue de producciónLa integración de OpenTelemetry es gratuita con Aspire
  • **Para casos de uso avanzado:**Los contenedores GPU permiten cargas de trabajo ML/AI con velocidad de 10-15x
  • Multi-arquitectura construye soporte ARM64 y AMD64 de una sola fuenteLas acciones de GitHub automatizan las construcciones multiplataforma
  • El caché de capas acelera dramáticamente las construcciones repetidasEl viajeLa evolución Docker de este blog refleja la progresión típica:
  • Etapa Servicios RAM Uso ComplejidadEtapa 1

(Julio 2024) App + Tunel ~300MB Simple

Etapa 2

(Hoy) + Aspire opción Variable Moderna

  1. El patrón: Comience simple, resolver los problemas a medida que surgen, añadir complejidad sólo cuando sea necesario.
  2. ¿Qué sigue?Dependiendo de su ruta:
  3. Aprender Docker: Comience con
  4. Docker Compose basics

Self-Hosting

: Echa un vistazo a la

Happy containerizing! 🐳

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.