Immersione profonda di sviluppo del docker: dalle basi alla Containerizzazione avanzata .NET (Italiano (Italian))

Immersione profonda di sviluppo del docker: dalle basi alla Containerizzazione avanzata .NET

Sunday, 09 November 2025

//

40 minute read

Introduzione

Docker ha fondamentalmente trasformato il modo in cui costruiamo, navighiamo ed eseguiamo le applicazioni.

Ciò che è iniziato come un semplice strumento di containerizzazione si è evoluto in un ecosistema completo per lo sviluppo e l'implementazione di applicazioni moderne.

  1. **Questo è il quarto e più completo articolo della mia serie Docker per gli sviluppatori .NET.**Se sei nuovo a Docker, potresti iniziare con:
  2. Docker Componi- Iniziare con le configurazioni di base multi-container (luglio 2024)
  3. Dipendenze di sviluppo con Docker Compose- Configurazione di ambienti dev locali (agosto 2024)

Condivisione immagini con Docker

  • **- Risoluzione dei problemi di autorizzazione del volume (agosto 2024)**In questa immersione profonda, costruiremo su quelle fondamenta per esplorare:
  • Componi Docker pronto per la produzione: Lo stack reale che esegue questo blog
  • Auto-ospitalità su risorse limitate: Tecniche pratiche di ottimizzazione per le implementazioni VPS di bilancio
  • Supporto GPU: Esecuzione di carichi di lavoro ML in contenitori
  • Costruzioni multi-architettura: Supporto ARM64 e AMD64
  • .NET 9 caratteristiche del contenitore: Pubblicazione integrata di container senza Dockerfiles

.NET Aspire

: L'approccio moderno .NET all'orchestrazione dei container

NOTA: Questo fa parte dei miei esperimenti con AI / un modo per spendere $1000 Claude Code Crediti Web.

Ho dato a questo un sacco di documenti, la mia comprensione, le domande che ho dovuto generare questo articolo.

E' divertente e riempie un vuoto che non ho visto riempito da nessun'altra parte.

Sia che stiate implementando una semplice web app o orchestrando un'architettura complessa di microservizi con modelli di apprendimento automatico accelerati dalla GPU, questa guida vi porta dalle basi di Docker alle applicazioni containerizzate pronte per la produzione - con esempi reali dall'esecuzione principalmentelucid.com.

  • Docker Fondamentals: Capire i contenitoriCos'e' Docker, davvero?
  • **Al suo centro, Docker è una piattaforma di containerizzazione che confeziona l'applicazione e tutte le sue dipendenze in un'unità standardizzata chiamata contenitore.**A differenza delle macchine virtuali che virtualizzano interi sistemi operativi, i container condividono il kernel host OS mantenendo spazi utente isolati.

Pensala in questo modo:

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

# The container solution
"Ship your machine!" 

Macchine virtuali

  1. : Ogni VM esegue uno stack completo del sistema operativo (Kernel Linux, librerie di sistema, ecc.) - pesante e lento all'avvioContenitori
  2. : Condividere il kernel host, pacchetto solo codice dell'applicazione e dipendenze - leggero e velocePerché Containers Materia per gli sviluppatori
  3. **I contenitori risolvono diversi problemi critici:**Coerenza ambientale
  4. : Lo sviluppo, i test e gli ambienti di produzione sono identiciIsolamento della dipendenza
  5. : Niente più "DLL hell" o versioni della libreria in conflittoBuild riproducibili

: Stesso input = stesso output, ogni volta

Distribuzione rapida

# 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

: Avviare contenitori in pochi secondi, non minutiEfficienza delle risorse

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

: Eseguire dozzine di contenitori su un singolo host

  • Concetti Docker essenzialiImmagini vs Contenitori
  • Immaginisono immutabili, filesystem a livelli.
  • **Ogni istruzione in un file Docker crea un nuovo livello:**Questo strato è potente:

CachingCity name (optional, probably does not need a translation)

: I livelli immutati vengono riutilizzati, accelerando i buildCondivisione

: Le immagini multiple possono condividere i livelli di base

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

Efficienza

# 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

: Solo i livelli modificati devono essere scaricati/caricati

  1. **Comprendere l'esecuzione di Dockerfile: Non la tua macchina!**Una fonte comune di confusione per i principianti Docker:COPYI comandi in un file Docker non girano sulla vostra macchina - girano all'interno del sistema operativo del contenitore build.ADDEcco cosa succede veramente:
  2. **Perche' questo e' importante:**Intuizioni chiave:RUNFilesystem locale
  3. : Il tuoe
  4. comandi letti dalla tua macchinaGenera immagine

: Il tuoapt-getcomandi eseguiti nel sistema operativo del contenitore (non nella tua macchina)

Immagine di output

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

: L'immagine finale contiene tutti i livelli creati durante la generazione

Piattaforma trasversale

: È possibile essere su Windows, la costruzione di un'immagine Linux, utilizzando i comandi 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"]

Questo è il motivo per cui puoi scrivere

comandi in un file Docker su Windows - non sono in esecuzione su 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

Stanno correndo all'interno del contenitore build basato su Linux.

  1. **Scenario di confusione di esempio:**Il demone Docker gestisce la traduzione tra il filesystem locale e l'ambiente di generazione containerizzato.
  2. Migliori pratiche DockerfileEcco un .NET Dockerfile pronto alla produzione con commento:.csprojComprendere il flusso di generazione
  3. **Ecco cosa succede in realtà durante una generazione Docker multi-stage - dove i file vengono da e dove finiscono:**Intuizioni chiave di questo flusso:npm run buildImmagine SDK scartata
  4. : Il contenitore SDK da 1,5GB viene buttato via dopo la pubblicazione - solo ~50MB di output compilato si muove in avanti: appsettings.jsonCaching dei livelliwwwroot/: Copia
  5. prima del codice sorgente significa che il ripristino della dipendenza è in cache a meno che le dipendenze non cambinoAttività di frontend
  6. : Costruito separatamente (spesso via) e copiata nell'immagine finale

File di configurazione

  1. econtenuto copiato dalla macchina, non costruito
  2. Immagine finale: Avvia dall'immagine runtime minimale (~220MB) + la tua app (~30MB) + le attività (~5MB) = ~255MB totale
  3. Solo file di produzione: Codice sorgente, obj/, bin/, nodo_modules/ mai arrivare all'immagine finale
  4. **Principi fondamentali illustrati:**Build multistadio
  5. : Gli stadi separati di compilazione/pubblicazione riducono drasticamente la dimensione finale dell'immagineOttimizzazione dei livelli

: Copiare i file di dipendenza prima del codice sorgente per una migliore cache

# 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

Sicurezza

: Esegui come utente non root

Controlli sanitari

: Container orchestratori possono monitorare la salute delle applicazioni

  • Ambiente esplicito
  • : Imposta impostazioni predefinite di produzione
  • Edificio e funzionamento
  • Docker Compose: Orchestra multi-container
  • Docker Compose consente di definire ed eseguire applicazioni multi-container.

Invece di gestire i container singolarmente, descrivi l'intero stack dell'applicazione in un file YAML.docker runPerché Docker Compose?

Considerare una tipica applicazione web .NET:

ASP.NET Core web app**Database PostgreSQLdocker-compose.yml**Cache Redis

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 per la registrazione

  1. Forse un servizio operaio di backgroundGestire questi singoli conapp_networkI comandi diventano ingombranti.
  2. **Docker Compose risolve questo problema.**Esempio completo di Real-World: Piattaforma di produzione del blog/mnt/*Ecco il
  3. Produzione effettivache gestisce proprio questo sito (per lo più Lucid.com):
  4. **Modelli di produzione chiave:**Rete unica
  5. : Tutti i servizi super semplicità
  6. Montature oblique: Percorsi di host (
  7. ) per i dati persistenti è necessario eseguire il backup/accessoVolumi con nome.env: Docker-managed storage per i dati non è necessario l'accesso diretto a

Etichette della torre di avvistamento

: Solo i servizi di aggiornamento esplicitamente etichettati per l'aggiornamento automatico

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

Limiti delle risorsecondition: service_healthy: I vincoli della CPU sul servizio di traduzione impediscono la fame delle risorse

Mappatura esterna dei porti

# .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 su 5266 invece di 5432 per evitare conflitti con altre istanze

File ambiente

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

: Segreti in:

  • file (mai impegnato in git)
  • Docker Componi funzionalità chiave
  • Dipendenze dal servizio
  • Docker Componi l'ordine di avvio degli orchestrati.

La:

  • richiede il controllo dello stato di salute del database per passare prima di avviare l'applicazione web.
  • Variabili e segreti dell'ambiente
  • Per i segreti di produzione, utilizzare Docker Secrets o manager segreti esterni (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault).

Volumi con nome contro montanti Bind

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

services:
  web:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend  # Not exposed to frontend

Volumi nominati

Gestito da Docker

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

Persistere i dati tra i riavvii del contenitore

  • Può essere eseguito il backup/ripristino con i comandi Docker
  • Cross-platform compatibile
  • Montanti obliqui

Mappatura diretta al filesystem 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

Utile per lo sviluppo (ricaricamento del codice live)

File di configurazione, log, upload

NetworkingLe reti forniscono isolamento.

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

**Qui, il database è accessibile solo ai servizi di backend, non direttamente esposti.**Controlli sanitari

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

**I controlli sanitari consentono a Docker di:**Determinare se un contenitore è effettivamente pronto (non appena iniziato)

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

Riavviare contenitori malsani

Fornire lo status agli orchestratori (Kubernetes, Docker Swarm)

Comandi di composizione Docker comuni

# 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

Sviluppo vs produzione Componi file

# 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"]

Problemi separati con più file di composizione:

# 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(sviluppo - autocombustione):docker-compose.prod.yml

(produzione):

  • Supporto GPU in Docker: Accelerare i carichi di lavoro MLL'apprendimento automatico, l'informatica scientifica e le applicazioni di elaborazione video richiedono spesso l'accelerazione della GPU.
  • **Docker supporta le GPU NVIDIA tramite NVIDIA Container Toolkit.**Impostazione di NVIDIA Container Toolkit
  • Dockerfile per l'applicazione Python/PyTorch accelerata GPUEseguire contenitori GPU
  • GPU in Docker ComponiEsempio di Real-World: servizio di traduzione con GPU e CPU Build

Ecco un esempio di produzione reale da

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:

per lo più Lucid-nmt

, un servizio di traduzione automatica neurale ho costruito che poteri auto-traduzione su questo 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:

Il progetto dimostra:

  • scottgal/mostlylucid-nmt:gpuVarianti GPU e CPU
  • scottgal/mostlylucid-nmt:cpu- Stessa base di codice, diverse immagini di base
  • scottgal/mostlylucid-nmt:gpu-minCostruzioni multi-architettura
  • scottgal/mostlylucid-nmt:cpu-min- Supporta AMD64 e ARM64

Immagini Docker ottimizzate

  • - varianti sia complete che minimaliPronti per la produzione
  • - Controlli sanitari, persistenza del volume, registrazione correttaServizio di traduzione accelerato da GPU
  • Alternativa solo per CPUPer gli ambienti senza GPU, lo stesso servizio viene eseguito sulla CPU:
  • Varianti dell'immagine disponibili:- CUDA 12.6 con supporto PyTorch GPU (~5GB)
  • - CPU-Solo, impronta più piccola (~2.5GB): /health- Minimale GPU build, nessun modelli precaricati (~4GB)/ready- Generazione minima della CPU (~1.5GB)
  • **Caratteristiche principali:**Accelerazione GPU

: 10-15x traduzione più veloce con CUDAModello Scaricamento automatico: Scarica i modelli di traduzione on-demand

Supporto di ripiegamento

: Prova Opus-MT → mBART50 → M2M100 per la massima copertura linguistica

Persistenza del volume

# 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 .

: Riavvia i modelli di cache attraverso i container

Endpoint della salute

# 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

e

per orchestratori

# 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

Multi-Architettura

# 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 \
  .

: Corre su x86_64 e ARM64 (Apple Silicon, Raspberry Pi)

Vedere il

progetto completo su 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

per esempi di Dockerfile, compilazione di script e documentazione 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-Architettura costruisce con Docker Buildx

Le applicazioni moderne devono funzionare su più architetture: x86_64 (AMD64) per i server, ARM64 per Raspberry Pi e Apple Silicon Macs, a volte anche ARM32 per i dispositivi incorporati.

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

Perché questioni di multi-architettura

  • Impostazione di Buildx
  • Docker Buildx è incluso nel Docker Desktop.
  • Per Linux:
  • File Docker multi-architettura
  • La maggior parte dei file Docker funzionano senza modifiche, ma ecco alcuni suggerimenti:

Costruzione di piattaforme multiple

Multi-Architettura con Docker Compose

Sfortunatamente, Docker Compose non supporta direttamente buildx.

Ricorsi di lavoro:

# 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

Opzione 1: Pre-build immagini

Opzione 2: Genera 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.

Esempio di Real-World: CI/CD Pipeline

dotnet run --project MyDistributedApp.AppHost

Il flusso di lavoro delle azioni GitHub per la costruzione di multi-architettura:

  • Questo flusso di lavoro:
  • Trigger su push a tag principale o versione
  • Imposta la QEMU per l'emulazione multipiattaforma
  • Costruisce per AMD64 e ARM64

Genera automaticamente i tag (nome del ramo, versioni semantiche, SHA)

Usa la cache del registro per costruire più velocemente

  • .NET 9 Miglioramenti dei contenitori
  • .NET 9 introduce miglioramenti significativi al supporto dei container, rendendo più facile che mai containerizzare le applicazioni .NET senza nemmeno scrivere un Dockerfile.
  • Built-in Container Publishing
  • Con .NET 9, è possibile pubblicare un'applicazione containerizzata direttamente:

Configurazione delle proprietà del contenitore

  • Aggiungi al tuo
  • Esegui tutto:
  • Aspire lancia:
  • Cruscotto all'indirizzo http://localhost:1588

Tutti i servizi con configurazione correttaRedis e PostgreSQL in contenitori

Tracciamento distribuito tra i servizi

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/

Linguaggio-agnostico

Infrastructure focused

// 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);

Scoperta del servizio manuale

Portare la propria osservabilità

Aspira:

.NET-specificoIncentrato sullo sviluppoScoperta automatica del servizio

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

Telemetria integrata

  • Genera i manifesti di distribuzioneUtilizzare entrambi
  • **: Aspire per lo sviluppo locale, genera Docker Compose/Kubernetes per la produzione.**Sfruttare le applicazioni Aspire
  • **Genera i manifesti di distribuzione:**Quindi dispiegare:

Componenti di aspirazioneLe integrazioni precostruite rendono banali i servizi aggiuntivi:Self-Hosting su risorse limitate: Ottimizzazione pratica

Eseguire un completo stack di osservabilità come quello di cui sopra richiede risorse significative.

Se siete auto-hosting su un VPS con 4GB di RAM o un vecchio computer portatile, ecco le strategie pratiche per ridurre il consumo di risorse mantenendo la funzionalità.

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:

Solo dipendenze di sviluppo

  • **Per lo sviluppo locale, non è necessario l'intero stack di produzione.**La
  • devdeps-docker-compose.ymll'approccio funziona solo di ciò di cui hai bisogno:docker logsPerché questo funziona per lo sviluppo:
  • SMTP4Dev: Funzionalità di test via email senza un vero server SMTP
  • PostgreSQL: Corrisponde al database di produzione
  • Impronta totale: ~200MB RAM vs 2-4GB per la pila completa

Vedere il

guida completa sulle dipendenze per lo sviluppo

per istruzioni di configurazione.

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

Configurazione di produzione basata sulle risorse

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

Per un VPS budget (2-4GB RAM), dare priorità ai servizi essenziali:

# Add everything
docker-compose up -d

Cosa viene rimosso e alternative:

No Prometheus/Grafana

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

**: Utilizzare il monitoraggio esterno (UptimeRobot, livello gratuito di BetterStack)**No Seq

: Utilizzare la registrazione basata su file +

, o livello Seq Cloud free

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

Nessuna torre di guardia: Aggiornamenti manuali con le notifiche delle Azioni GitHub

Nessun servizio di traduzione

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

: Eseguire on-demand in un contenitore separato si avvia/stop manualmente

Limiti delle risorse

: Impedire a qualsiasi singolo servizio di consumare tutta la memoriaStrategia di miglioramento progressivoAvviare minimal, aggiungere servizi se necessario:

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

**Fase 1: nucleo (512MB-1GB VPS)**Fase 2: aggiungere osservabilità (2GB VPS)

Fase 3: Stack completo (4GB+VPS)

Tecniche di ottimizzazione delle risorse |---------|-----------------|---------------------| 1. Usa immagini alpine Risparmi

**: Le immagini alpine sono più piccole del 50-70%**2.

Caso di banca dati condivisa

Invece di un database per servizio, utilizzare un'istanza PostgreSQL con più database:

# 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

Risparmi: 400MB RAM per database aggiuntivo si consolida

3.

Limitare la CPU per i servizi di background

# 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

Questo impedisce ai lavori di background di morire di fame l'applicazione web.

  • Volume cache persistente
  • Coperto in

Condivisione immagini con Docker

  • , le directory della cache di montaggio impedisce il ritrattamento inutile:
  • Perche' e' importante?docker logs: Senza questo, ogni riavvio del contenitore rigenera tutte le miniature/immagini elaborate.
  • Utilizzare i servizi esterni (tire libere)
  • | Servizio | RAM self-hosted | Alternativa esterna |

| Seq | ~ 500MB | Seq Cloud (10GB/mese libero) |

| Prometheus + Grafana | ~ 600MB | Cloud di Grafana (livello libero) |

| Umami | ~ 200MB | Plausibile (pagato) o auto-host altrove |

# 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

Strategia

# 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

: Offload osservability to free tiers, keep core application on your VPS.

# Quick resource check
docker stats --no-stream

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

6.

Servizi su richiesta

Per i servizi usati di rado come la traduzione:

Risparmi

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

: 1-2GB RAM quando il servizio di traduzione non è in esecuzione

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();

Esempio di hosting a budget reale mondiale

Ecco cosa funziona su un $6/mese Hetzner VPS (2 vCPU, 4GB di RAM):**Uso totale delle risorse:**RAM: ~800MB (lascia libero 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% inattivo, <50% sotto carico

Cosa c'e' di diverso:

  1. Nessuna Prometheus/Grafana (usare UptimeRobot + Cloudflare Analytics): dotnet run --project Mostlylucid.AppHostNo Seq (uso
  2. **+ grep occasionale)**No Umami (usare Cloudflare Web Analytics - gratuito)
    • Watchtower controlla ogni ora invece di ogni 5 minuti
    • Nessun servizio di traduzione (eseguire manualmente quando necessario)
    • Monitoraggio di un bilancio
    • Senza Prometheus/Grafana, utilizzare queste alternative gratuite:
  3. **Monitoraggio sanitario:**Monitoraggio dei registri:
  4. **Uso delle risorse:**Aspire Version: La via .NET.envOra riimmaginiamo l'intero stack usando .NET Aspire.

Questo ti dà tutti i vantaggi di orchestrazione con una migliore integrazione .NET e una sorprendente esperienza di sviluppatore.

Impostazione dell'aspirazione per la maggior parte deilucidi

# 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

**In primo luogo, creare l'host Aspire App:**Per lo più Lucid.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

Predefiniti servizi di aspirazione

Crea |---------|---------------|-------------| | Predefiniti serviziprogetto: | Aggiorna per lo più Lucid/Program.csVantaggi dell'approccio Aspire | **Esperienza di sviluppo:**Comando singolo | inizia tutto | docker-compose up | dotnet runCruscotto | **: Beautiful UI at http://localhost:1588 mostrando:**Tutti i servizi con stato di vita | Logs da tutti i servizi in un unico luogo | docker logsTracce distribuite tra i servizi | Metrics and health checksService Discovery | : I servizi si trovano automaticamente tramite i nomiConfigurazione | : Centralizzato in AppHost, non piùgiocoleria

Distribuzione della produzione:

Genera i manifesti di distribuzione da Aspire:

  • Generato docker-compose.yml
  • (semplificato):
  • Aspira vs Docker tradizionale Componi
  • | Caratteristica | Docker Compose | .NET Aspire |
  • Configurazione

| YaML manuale | codice C# con IntelliSense |

  • Service Discovery
  • | Variatori di tensione manuali | Automatico |
  • Osservabilità
  • | Porta il tuo | Built-in (OpenTelemetry) |
  • Politica di sviluppo

+ Cruscotto |

  • Debug
  • | Attacco al contenitore | F5 in Visual Studio |
  • Registri

| Cruscotto centralizzato |

Rintracciamento

  1. **| Setup manualmente | Automatic distributed tracing |**Produzione
  2. **| Usare direttamente YAML | Generare manifesti |**Curve di apprendimento
  3. **| Sintax YAML | C# you already know |**Quando usare Aspire vs Docker Compose
  4. **Usa Aspire quando:**Costruzione di microservizi .NET
  5. Vuoi il debug integratoIl team è a suo agio con C#

È necessario distribuito tracciando fuori-della-scatola

  • Stai distribuendo ad Azure Container Apps (supporto nativo)
  • Usa Docker Compose quando:
  • Servizi Polyglot (Node.js, Python, Go, ecc.)
  • Il team preferisce l'infrastruttura come codice YAML
  • Distribuzione a qualsiasi host compatibile con Docker
  • È necessario il massimo controllo sulla configurazione del contenitore

Semplici implementazioni in un unico servizio

Utilizzare entrambi:

  1. Aspira allo sviluppo locale
  2. Genera Docker Componi per l'implementazione della produzione
  3. Il meglio di entrambi i mondi!
  4. Evoluzione della configurazione di Docker di questo bloglatest)
  5. Il viaggio di Docker di questo blog mostra una progressione tipica:
  6. Luglio 2024 - Inizio semplice.dockerignore: Solo l'app, Cloudflared, e Torre di Controllo
  7. Agosto 2024 - Dev Dipendenze
  8. : Aggiunti solo servizi di sviluppo

Agosto 2024 - ImageSharp Fix

  1. : Risolto i permessi di volume per la cache
  2. Novembre 2024 - Stack completo
  3. : Completa osservabilità con Prometheus, Grafana, Seq.envOggi
  4. : Opzione di aspirazione per lo sviluppo .NET-first
  5. Lezioni imparate:depends_onAvviare semplice, aggiungere complessità solo quando necessario
  6. Configurazioni di dev e produzione separate
  7. I limiti delle risorse impediscono ad un servizio di uccidere gli altri
  8. Montaggio volume per cache risparmio significativo tempo di ritrattamento

Watchtower consente l'aggiornamento automatico zero-downtime

  1. L'osservabilità vale il costo delle risorse nella produzione
  2. Sintesi delle migliori pratiche
  3. Migliori pratiche Dockerfile
  4. Usa build multi-stage
  5. Esegui come utente non root
  6. Ordinare le istruzioni per la cache ottimale
  7. Usa tag immagine base specifici (non
  8. Includere i controlli sanitari

Uso

  1. per escludere i file non necessari
  2. Minimizza i livelli (combina i comandi RUN)
  3. Usa gli argomenti build per la flessibilità
  4. Docker Compose Best Practice.dockerignoreUsa i volumi nominati per i dati
  5. Attuare i controlli sanitari
  6. Uso
  7. per i segreti (mai commettere)
  8. Definire reti esplicite

Uso

con condizioni di salute

# 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

Imposta le politiche di riavvio

# 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 .

Configurazioni separate 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

Limiti di risorse nella produzione

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

Migliori pratiche in materia di sicurezza

Esegui come utente non root

Usa immagini scalpellite/minimali di base

Scansiona le immagini per le vulnerabilità

Uso

  • generosamente
  • Immagini alpine / scalpelli per dimensioni più piccole
  • Usa supporti volume per lo sviluppo
  • Configurare i limiti di risorse appropriati
  • Utilizzare controlli sanitari per l'orchestrazione

Risoluzione dei problemi comuni

  • Il contenitore non parte
  • Generazioni lente
  • Questioni legate alla rete
  • Emissioni di permessi in volume
  • Conclusione
  • Docker si è evoluto da un semplice strumento di containerizzazione a una piattaforma completa per la costruzione, il trasporto e l'esecuzione di applicazioni moderne.

Questa guida vi ha portato dai fondamentali agli schieramenti pronti per la produzione, con esempi reali da eseguire principalmentelucid.com.

  • Key TakeawaysCity name (optional, probably does not need a translation)
  • Per principianti:
  • Inizia con il
  • Docker di base Componi configurazione
    • solo 3 servizi

Uso

  • dipendenze solo per lo sviluppo
  • per il lavoro locale
  • Risolvere problemi comuni come
  • permessi volume

presto

Per gli auto-hoster sul VPS di bilancio:

Start minimal: App + Database + Reverse Proxy (~800MB RAM) |-------|----------|-----------|------------| | Utilizzare immagini alpine e limiti di risorseScarica l'osservabilità ai livelli liberi (Seq Cloud, Grafana Cloud, UptimeRobot) | Eseguire servizi costosi (traduzione, ML) solo su richiestaUn VPS $6/mese può eseguire un blog di produzione con spazio per risparmiare | **Per la distribuzione della produzione:**I controlli di salute consentono aggiornamenti zero-downtime con Watchtower | **Reti separate forniscono sicurezza (isolamento frontend/backend)**Montaggio di bordi per i dati necessari per il backup/accesso | Volumi nominati per Docker-managed storageI limiti CPU/memoria impediscono la fame delle risorse

Cloudflare Tunnel elimina la necessità di indirizzi IP pubbliciPer gli sviluppatori .NET:

Built-in container publishing in .NET 9 elimina Dockerfiles per applicazioni semplici

Le immagini intagliate di Ubuntu forniscono una superficie d'attacco minima

  • .NET Aspire offre la migliore esperienza di sviluppo locale di classeAspire può generare Docker Compose per l'implementazione della produzioneL'integrazione OpenTelemetry è gratuita con Aspire
  • **Per casi di utilizzo avanzato:**I contenitori GPU consentono carichi di lavoro ML/AI con velocità di 10-15x
  • Multi-architettura costruisce il supporto ARM64 e AMD64 da singola fonteLe azioni GitHub automatizzano i build multipiattaforma
  • La cache dei livelli accelera notevolmente le build ripetuteIl viaggioL'evoluzione di Docker di questo blog rispecchia la progressione tipica:
  • **| Stadio | Servizi | Uso RAM | Complessità |**Fase 1

(luglio 2024) | App + Tunnel | ~ 300MB | Simple |

Fase 2

(Oggi) | + Opzione di aspirazione | Variabile | Moderno |

  1. Il modello: Avviare semplice, risolvere i problemi come si presentano, aggiungere complessità solo quando necessario.
  2. Cosa c'e' dopo?A seconda del percorso:
  3. Imparare Docker: Inizia con
  4. Docker Componi le basi

Auto-Hosting

: Dai un'occhiata al

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.