Докер докорінно змінив те, як ми будуємо, на кораблі і запускаємо програми.
Те, що почалося, як простий інструмент контейнеризації, еволюціонувало в повну екосистему для сучасного розвитку та розвитку програм.
Розширення різкості зображень за допомогою DockerName
. NET Aspire
: Сучасний NET - підхід до ансамблю.
Это весело и заполняет прорыв, который я больше не видел.
Чи ви впроваджуєте просту веб-програму, чи керуєте складною мікрослужбовою архітектурою з моделями для машинного навчання GPU, цей гід відводить вас від основ Docker до програм, готових до виробництва, - з реальними прикладами від запуску quallucid.com.
# The classic developer problem
"It works on my machine!"
# The container solution
"Ship your machine!"
Віртуальні машини
# 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
: Запускати контейнери у секундах, а не у хвилинахЕфективність ресурсу
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
: Запустити десятки контейнерів на одному вузлі
: Незмінені шари буде повторно використано, збільшення швидкості збирання
# 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"]
Спільне користування
# 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
Перевірка здоров'я
Явне середовище
Чому дошки з'єднані?docker runРозглянемо типову веб- програму.NET:
База даних PostgreSQL**Кеш Redisdocker-compose.yml**Сек для ведення журналу
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
Може бути, послуга працівника на фоні.
app_networkДокер Комос вирішує це./mnt/*поточне виробництво.envМітки " Вартової башти "services:
web:
depends_on:
db:
condition: service_healthy # Wait for health check
redis:
condition: service_started # Just wait for start
: Обмеження процесора на перекладацьку службу запобігають голоду.condition: service_healthyПриписування зовнішніх портів
# .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
Файли середовища
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
файл (ніколи не передано git):
Перш ніж запускати веб-програму, потрібно пройти перевірку здоров'я бази даних.:
networks:
frontend:
driver: bridge
backend:
driver: bridge
services:
web:
networks:
- frontend
- backend
db:
networks:
- backend # Not exposed to frontend
Керується комбінацією
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Можна створити резервну копію за допомогою команд Docker
# 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
Робота у мережі
**Мережа забезпечує ізоляцію.**Тут база даних доступна лише для служб серверів, а не безпосередньо.
services:
web:
build: .
environment:
- ASPNETCORE_ENVIRONMENT=Development
Перевірка здоров'яДокер може перевіряти стан здоров'я:
services:
web:
volumes:
- .:/app # Live code reloading
ports:
- "5000:8080"
**Визначає, чи є контейнер насправді готовим (не лише запуском)**Перезапустити нездорові контейнери
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
Звичайні команди докерів
# 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
# 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"]
# 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
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
(розробка - автоматичне об'єднання):docker-compose.prod.yml(продукція):
Підтримка GPU в Docker: Завантаження роботи Acceatith ML
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:
Проект продемонстрирует:
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:
Варіанти GPU і процесора
scottgal/mostlylucid-nmt:gpu- Та сама база коду, різні базові зображенняscottgal/mostlylucid-nmt:cpuБагатоархічне збиранняscottgal/mostlylucid-nmt:gpu-min- Підтримує AMD64 і ARM64scottgal/mostlylucid-nmt:cpu-minОптимізовані зображення Docker- Повних і мінімальних варіантів
/health- Мінімальне збирання процесора (~1. 5GB)/readyМожливості ключів:Модель Автозавантаження: Звантажує моделі перекладу на- demandПідтримка зворотного стану
Наполегливість у об' ємі
# 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 .
і
# 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
Багатоархітектура
# 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
# 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 \
.
повний проект GitHub
Для прикладів Dockerfile, збирання скриптів і документації з API.
# 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
Мульти- архівний збірок з Docker ЗбиранняxName
#!/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
Чому багатоархічні питання
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
Налаштування збирання
К сожалению, Докер Компос не поддерживает стройку.
Параметр 1: Попереднє збирання зображень
# 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
Приклад реального світу: CI/CD Cileline.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.source">https://github.com/me/myapp</ContainerLabel>
</PropertyGroup>
</Project>
Процес роботи з діями GitHub для збирання багатоархічних структур:
dotnet publish -p:PublishProfile=DefaultContainer
<PropertyGroup>
<!-- Enable trimming to remove unused code -->
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<!-- OR use Native AOT for even smaller, faster images -->
<PublishAot>true</PublishAot>
</PropertyGroup>
Тоді опублікуйте:
Безпека
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
: Автоматичне оновлення базових зображень
# Just this!
dotnet publish -p:PublishProfile=DefaultContainer
Менші зображення
Об' єднання і AT для менших зображень
: ~110MБ
Обидва створюють подібні зображення, але вбудований підхід простіший і придатний для збереження.
<PropertyGroup>
<ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled</ContainerBaseImage>
</PropertyGroup>
Коли використовувати файл Dockerfile/s вбудовано
Стандартні веб- програми. NET
Швидке прототипування
. NET 9 підтримує " обрізані " зображення Ubuntu - надмінні базові зображення:
Крихітний
Вважайте це Докер Компосом на стероїдах, спеціально розробленим для послуг мікрослужб.
: Вбудована лісозаготівля, метрики, слідкування
dotnet new aspire-starter -n MyDistributedApp
cd MyDistributedApp
Компоненти
var builder = DistributedApplication.CreateBuilder(args);
// Add Redis cache
var cache = builder.AddRedis("cache");
// Add PostgreSQL database
var db = builder.AddPostgres("postgres")
.AddDatabase("mydb");
// Add API service (references cache and db)
var api = builder.AddProject<Projects.MyApi>("api")
.WithReference(cache)
.WithReference(db);
// Add frontend (references API)
builder.AddProject<Projects.MyWeb>("web")
.WithReference(api);
builder.Build().Run();
: Попередньо налаштовані інтеграції (Redis, PostgreSQ, RockMQ тощо)
// MyApi/Program.cs
var builder = WebApplication.CreateBuilder(args);
// Aspireing
3. Multi-stage builds to reduce image size
4. Use `.dockerignore` generously
5. Alpine/chiseled images for smaller size
6. Use volume mounts for development
7. Configure appropriate resource limits
8. Use health checks for orchestration
## Troubleshooting Common Issues
### Container Won't Start
```bash
# 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
# 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 .
# 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
# Permission denied on volume
# Solution: Match user IDs
FROM ubuntu
RUN useradd -u 1000 appuser # Match host user ID
USER appuser
Вузол App
Повільні збирання
Happy containerizing! 🐳 Будування програм
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
: Vererage Docker Compose для багатослужбового оркестрування
Створення мікрослужб: Explore.NET Aspire для розподілених програмЕкосистема контейнера надає інструменти для кожного сценарію.
Додаткові ресурси
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:
Документація Docker
docker logsDocker buildx: ~200MБ ОЗП проти 2-4GB для всього стеку
Видите
# Just app + database + reverse proxy
docker-compose up -d mostlylucid db caddy
Повноцінний напрямник залежностей розвитку
# Add lightweight monitoring
docker-compose up -d mostlylucid db caddy seq
# Use Seq free 10GB/month
для налаштування інструкцій.
# Add everything
docker-compose up -d
services:
myapp:
image: postgres:16-alpine # 50% smaller than postgres:16
# vs
image: postgres:16 # Full Debian base
**Видалені та альтернативи:**No Prometheus/Grafana
Без Seq
db:
image: postgres:16-alpine
# One instance, multiple databases
# Umami, Mostlylucid, etc. all share this PostgreSQL
: Використовувати журналювання на основі файлів +, або пам' ять Сек- Хмара
easynmt:
deploy:
resources:
limits:
cpus: "2.0" # Don't let translation consume all CPU
reservations:
cpus: "0.5" # Guarantee minimum
: Ручні оновлення за допомогою сповіщень про дії GitHub
: Запустити on- demand у окремому контейнері, який ви запускаєте/ зупиніть вручнуОбмеження ресурсів: Захищати будь- яку окрему службу від споживання всієї пам' яті
mostlylucid:
volumes:
- /mnt/imagecache:/app/wwwroot/cache # ImageSharp cache persists across restarts
Стратегія прогресивного зростанняПочати мінімальне, додати служби за потреби:
Стадія 2: Додати спостереження (2GB VPS) |---------|-----------------|---------------------| Стек 3: Повний стек (4GB+ VPS) Техніка оптимізації ресурсів 1.
Використовувати альпійські зображенняОщадний
# 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
Спільний внесок до бази данихЗамість однієї бази даних на службу, скористайтеся одним екземпляром PostgreSQL з декількома базами даних:
: 400MБ ОЗП на додаткову базу даних
# 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
3.
Постійні об' єми кешу
docker logs, каталоги монтування кешу забороняють непотрібну обробку:♪Value ♪
** Seq 500MG}Чарівник Seq (10GB/Mail free) **
# Simple health check script
#!/bin/bash
while true; do
curl -f http://localhost/healthz || echo "Health check failed!" | mail -s "Alert" you@example.com
sleep 300
done
♪ Prometheus + Grafana ♪600M] Grafana Хмара (free blustr) ♪
# 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" you@example.com
done
** Уммі---- 200 Мб} (оплачено) або сам- вузол десь в іншому місці}**
# Quick resource check
docker stats --no-stream
# Pretty output
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
: Не перевантажуйте так зв'язку, щоб у вас не було тіста, залиште ключову програму на VPS.
Служби " Вмикання" Comment
# Add Aspire to your solution
dotnet new aspire-apphost -n Mostlylucid.AppHost
cd Mostlylucid.AppHost
Для нечасто використовуваних служб, таких як переклад:
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();
: 1- 2GB RAM, якщо не запущено службу перекладуПриклад використання бюджету у реальному світіОсь що працює на Hetzner VPS (2 vCPU, 4GB RAM):
// 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;
}
}
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();
Диск: ~2GB
dotnet run --project Mostlylucid.AppHostЩо відрізняється:.envВикористання Resource:Aspire Version: The.NET WY
Тепер давайте передумаємо, що весь стек використовує NET Aspire.
# 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
**Це дає вам всі переваги оркестрування з кращою інтеграцією NET та чудовим досвідом розробника.**Встановлення переважного значення
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
usiculcid. AppHost/Program.cs:
|---------|---------------|-------------|
| Типові значення служби AspireСтворити
| Типове значення usiclucid.Serviceпроект:
| Оновити здебільшого у форматі program.csКористь від постійного наближення
| Досвід розвитку: | docker-compose up | dotnet runОдна команда
| розпочати всеПанель приладів
| : Прекрасний інтерфейс на http:// localhost: 15888 showing: | docker logsВсі служби з живим станом
| Журнали всіх служб в одному місціРозподілені сліди за службами
| Метрики та обстеження здоров'яЗнаходження служб
| : Служби знаходять одне одного автоматично за допомогою іменНалаштування
жонглювання
Дзвінок Докконоза. НЕТ Аспірі
♪ Sheep your self's core- in (OpenTemepry) ♪
Журнали
Вам потрібне інтегроване зневадження
latest).dockerignoreУ цьому блозі показано типовий прогрес:.envЛистопад 2024 - Повний стекdepends_on: Аспірувати параметр для розробки. NET- першого.dockerignoreДля гнучкості використовуйте аргументи збирання# 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
# 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 .
# 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
# Permission denied on volume
# Solution: Match user IDs
FROM ubuntu
RUN useradd -u 1000 appuser # Match host user ID
USER appuser
Обмеження ресурсів у виробництві
Виконати як неroot користувач
Кечування шару вереражу
Налаштувати відповідні обмеження ресурсів
Висновки
Базове налаштування композитного набору Docker
права доступу тому
ранній |-------|----------|-----------|------------| | **Для само- Хостерів у бюджеті VPS:**Почати мінімальне: App + База даних + Зворотний проксі (~800МБ ОЗП) | Використовувати альпійські зображення і обмеження ресурсівОб' єм пам'яті для вільного тирсу (Хмара Сек), Хмара Ґрафана, ЧасовийРобот) object name (optional) | Запустити дорогі служби (переклад, ML) лише наVPS на $6/місяця може працювати в виробничому блозі, де є більше місця. | **Для виробництва:**Перевірка здоров'я уможливила оновлення нульового часу з " Вартовою Баштою " | **Відокремлені мережі забезпечують безпеку (з' єднання зі зворотним з' єднанням)**Змонтовані прив' язки для даних, які вам потрібні для створення резервної копії/ доступу
Іменовані томи для сховища з керуванням DockerОбмеження процесора/ пам' яті запобігають голодові ресурсів
Для розробників. NET:
Крок No1
(Nov 2024)}
: Почати з
: Explore.NET Aspire
Happy containerizing! 🐳
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.