Claude-skill-registry docker-dotnet-containerize
Generate production-ready Docker configurations for .NET APIs with multi-stage builds, Alpine optimization, layer caching, and build scripts. Use when containerizing .NET applications, creating Dockerfiles, or optimizing existing Docker setups.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/docker-dotnet-containerize" ~/.claude/skills/majiayu000-claude-skill-registry-docker-dotnet-containerize && rm -rf "$T"
skills/data/docker-dotnet-containerize/SKILL.md.NET Docker Containerization Skill
Generate optimized Docker configurations for .NET projects using advanced build techniques, progressive layer publishing, and production-ready multi-stage builds.
What This Skill Does
I will analyze your .NET solution and generate:
- Optimized Dockerfile with BuildKit features and layer caching
- Build scripts (Bash/PowerShell) with version tagging
- .dockerignore file with comprehensive patterns
- Validation checklist and troubleshooting guidance
Advanced Techniques Applied
BuildKit Frontend (syntax=docker/dockerfile:1-labs)
I use the experimental BuildKit frontend for:
flag support (preserves directory structure)--parents- Better caching mechanisms
- Advanced COPY operations
- Improved build performance
Progressive Layer Publishing
For complex projects, I publish in dependency order:
- Domain layer → Publish first (most stable)
- Infrastructure/EF Core → Publish second
- Application/HttpApi → Publish third
- API Host → Publish last (changes most)
Why? This creates separate layers in
/app/publish, optimizing Docker layer caching. When you change only the API code, earlier layers remain cached.
Non-Alpine SDK with Alpine Runtime
- Build stage: Uses full SDK (not Alpine) for better compatibility
- Runtime stage: Uses Alpine for minimal footprint
- Benefit: Avoid Alpine SDK build issues while keeping final image small
Project Analysis
Detection Process
I'll examine:
- Solution file (
) location and structure*.sln - All project files (
) and their dependencies*.csproj - Main entry point (typically
,*.Host
,*.Api
)*.HttpApi.Host - .NET version from
tags<TargetFramework> - Existence of
(ABP Framework indicator)common.props - Project architecture (Simple, DDD, ABP, Clean Architecture)
Dependency Graph Mapping
I'll build a dependency graph to determine:
- Which projects reference which
- Optimal layer ordering for caching
- Whether progressive publishing is beneficial
Simple projects (≤3): Single publish step
Complex projects (≥4): Progressive multi-layer publishing
Dockerfile Generation
Standard Template Structure
# syntax=docker/dockerfile:1-labs # BuildKit frontend for advanced features (--parents flag) # Runtime base: Alpine for minimal size FROM mcr.microsoft.com/dotnet/aspnet:{VERSION}-alpine AS base USER app WORKDIR /app EXPOSE 8080 EXPOSE 8081 # Build stage: Full SDK (not Alpine) for compatibility FROM mcr.microsoft.com/dotnet/sdk:{VERSION}-alpine AS publish ARG BUILD_CONFIGURATION=Release WORKDIR /src # [Project-specific COPY and publish commands] # Final runtime FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "{MainAssembly}.dll"]
Pattern 1: Simple Projects (2-3 projects)
# syntax=docker/dockerfile:1-labs FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS base USER app WORKDIR /app EXPOSE 8080 EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS publish ARG BUILD_CONFIGURATION=Release WORKDIR /src # Copy project files with --parents (preserves structure) COPY --parents src/MyProject.Models/MyProject.Models.csproj \ src/MyProject.Api/MyProject.Api.csproj \ /src/ # Restore dependencies (quiet mode) RUN dotnet restore "./src/MyProject.Api/MyProject.Api.csproj" -v q # Copy all source code COPY --parents src/MyProject.Models/ \ src/MyProject.Api/ \ /src/ # Single publish step RUN dotnet publish "src/MyProject.Api/MyProject.Api.csproj" \ -c $BUILD_CONFIGURATION \ -o /app/publish \ /p:UseAppHost=false \ -v q FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "MyProject.Api.dll"]
Pattern 2: ABP Framework / Complex DDD (7+ projects)
# syntax=docker/dockerfile:1-labs FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base USER app WORKDIR /app EXPOSE 8080 EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS publish ARG BUILD_CONFIGURATION=Release WORKDIR /src # Copy solution-level configuration (ABP Framework) COPY common.props ./ # Layer 1: Copy all project files for restore COPY --parents src/Project.Domain.Shared/Project.Domain.Shared.csproj \ src/Project.Domain/Project.Domain.csproj \ src/Project.EntityFrameworkCore/Project.EntityFrameworkCore.csproj \ src/Project.Application.Contracts/Project.Application.Contracts.csproj \ src/Project.HttpApi/Project.HttpApi.csproj \ src/Project.Application/Project.Application.csproj \ src/Project.HttpApi.Host/Project.HttpApi.Host.csproj \ /src/ # Restore from entry point (restores all dependencies) RUN dotnet restore "./src/Project.HttpApi.Host/Project.HttpApi.Host.csproj" -v q # Layer 2: Publish Domain + EF Core (most stable, changes least) COPY --parents src/Project.Domain.Shared/ \ src/Project.Domain/ \ src/Project.EntityFrameworkCore/ \ /src/ RUN dotnet publish "src/Project.EntityFrameworkCore/Project.EntityFrameworkCore.csproj" \ -c $BUILD_CONFIGURATION \ -o /app/publish \ /p:UseAppHost=false \ -v q # Layer 3: Publish Application.Contracts + HttpApi COPY --parents src/Project.Application.Contracts/ \ src/Project.HttpApi/ \ /src/ RUN dotnet publish "src/Project.HttpApi/Project.HttpApi.csproj" \ -c $BUILD_CONFIGURATION \ -o /app/publish \ /p:UseAppHost=false \ -v q # Layer 4: Publish Application + Host (changes most often) COPY --parents src/Project.Application/ \ src/Project.HttpApi.Host/ \ /src/ RUN dotnet publish "src/Project.HttpApi.Host/Project.HttpApi.Host.csproj" \ -c $BUILD_CONFIGURATION \ -o /app/publish \ /p:UseAppHost=false \ -v q FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "Project.HttpApi.Host.dll"]
Key Optimization Techniques
1. --parents Flag (BuildKit)
# Preserves directory structure automatically COPY --parents src/Domain/Domain.csproj /src/ # Result: /src/src/Domain/Domain.csproj (structure maintained)
Why? No need to manually match paths. Works with relative references in
.csproj files.
2. Progressive Publishing Strategy
Traditional approach (single publish):
RUN dotnet publish "Host.csproj" -o /app/publish
❌ Changes to Host trigger rebuild of entire application
Progressive approach (layered publishing):
# Publish Domain (layer 1) RUN dotnet publish "Domain.csproj" -o /app/publish # Publish Infrastructure (layer 2) RUN dotnet publish "Infrastructure.csproj" -o /app/publish # Publish Application (layer 3) RUN dotnet publish "Application.csproj" -o /app/publish # Publish Host (layer 4) RUN dotnet publish "Host.csproj" -o /app/publish
✅ Changes to Host only rebuild layer 4, cache layers 1-3
3. Quiet Mode Builds (-v q)
RUN dotnet restore "./Project.csproj" -v q RUN dotnet publish "Project.csproj" -v q
Why? Cleaner build logs, easier to spot errors, less noise in CI/CD.
4. Alpine Runtime with Full SDK
# Build: Full SDK for better compatibility FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS publish # Runtime: Alpine for minimal size FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS base
Why? Alpine SDK can have issues with certain NuGet packages. Full SDK works better, Alpine runtime keeps final image small.
5. Build Configuration as ARG
ARG BUILD_CONFIGURATION=Release RUN dotnet publish -c $BUILD_CONFIGURATION
Why? Allows
docker build --build-arg BUILD_CONFIGURATION=Debug for testing.
Build Script Generation
Bash Script (build.sh)
#!/bin/bash # Build script for .NET Docker image with version tagging if [ -z "$1" ]; then echo "************************************************" echo "" echo "Usage: ./build.sh <version>" echo "Example: ./build.sh 1.0.0" echo "" echo "************************************************" exit 1 fi VERSION=$1 IMAGE_NAME="mycompany/myproject" DOCKERFILE_PATH="./Dockerfile" echo "Building Docker image: ${IMAGE_NAME}:${VERSION}" docker build \ -f ${DOCKERFILE_PATH} \ -t ${IMAGE_NAME}:${VERSION} \ -t ${IMAGE_NAME}:latest \ --build-arg BUILD_CONFIGURATION=Release \ . if [ $? -eq 0 ]; then echo "" echo "✅ Build successful!" echo "Image tagged as:" echo " - ${IMAGE_NAME}:${VERSION}" echo " - ${IMAGE_NAME}:latest" echo "" echo "To run: docker run -p 8080:8080 ${IMAGE_NAME}:${VERSION}" else echo "" echo "❌ Build failed!" exit 1 fi
PowerShell Script (build.ps1)
# Build script for .NET Docker image with version tagging param( [Parameter(Mandatory=$true)] [string]$Version ) $ImageName = "mycompany/myproject" $DockerfilePath = "./Dockerfile" Write-Host "Building Docker image: ${ImageName}:${Version}" -ForegroundColor Cyan docker build ` -f $DockerfilePath ` -t "${ImageName}:${Version}" ` -t "${ImageName}:latest" ` --build-arg BUILD_CONFIGURATION=Release ` . if ($LASTEXITCODE -eq 0) { Write-Host "" Write-Host "✅ Build successful!" -ForegroundColor Green Write-Host "Image tagged as:" Write-Host " - ${ImageName}:${Version}" Write-Host " - ${ImageName}:latest" Write-Host "" Write-Host "To run: docker run -p 8080:8080 ${ImageName}:${Version}" } else { Write-Host "" Write-Host "❌ Build failed!" -ForegroundColor Red exit 1 }
.dockerignore Generation
# Build outputs **/bin/ **/obj/ **/out/ **/publish/ # IDE and editor files **/.vs/ **/.vscode/ **/.idea/ **/*.user **/*.suo **/*.swp **/.DS_Store # Test results and coverage **/TestResults/ **/coverage/ **/*.trx # Package directories **/node_modules/ **/packages/ **/bower_components/ # Logs and temporary files **/*.log **/logs/ **/temp/ **/tmp/ # Version control .git/ .gitignore .gitattributes # CI/CD .github/ .gitlab-ci.yml azure-pipelines.yml # Documentation *.md !README.md docs/ documentation/ # Docker files (avoid recursion) **/Dockerfile* **/docker-compose* **/.dockerignore # Development tools **/.editorconfig **/.prettierrc **/.eslintrc*
Decision Logic for Dockerfile Patterns
When to Use Single Publish
✅ Use for:
- Projects with ≤3 .csproj files
- Simple API + Models structure
- Microservices with minimal dependencies
- Fast build times (<30 seconds)
When to Use Progressive Publishing
✅ Use for:
- Projects with ≥4 .csproj files
- ABP Framework projects
- Clean Architecture / DDD projects
- Long build times (>1 minute)
- Frequent changes to outer layers (API/Host)
Progressive publishing trades:
- Slightly more complex Dockerfile
- For significantly faster rebuild times
Architecture-Specific Patterns
ABP Framework Detection
Indicators:
file existscommon.props- Projects named with
,.Domain.Shared
suffixes.HttpApi.Host - 7+ projects in solution
Special handling:
# Copy common.props first COPY common.props ./ # Follow ABP layer order # Domain.Shared → Domain → EF Core → Contracts → HttpApi → Application → Host
Clean Architecture Detection
Indicators:
- Projects in
,src/Domain/
,src/Application/
,src/Infrastructure/
structuresrc/WebApi/ - 4-6 projects typically
Layer order:
Domain → Application → Infrastructure → WebApi
Simple API Detection
Indicators:
- 2-3 projects total
- Names like
,*.Models
,*.Api*.Data
Strategy: Single publish, no progressive layers needed.
Validation Checklist
After generation, I'll verify:
- BuildKit syntax directive present (
)# syntax=docker/dockerfile:1-labs - .NET version matches project
<TargetFramework> - Alpine images used for SDK
- Non-root user configured (
)USER app - Ports correctly exposed (8080, 8081)
-
flag used in COPY commands--parents - Projects ordered by dependency (inner → outer)
- Progressive publishing for complex projects (≥4 projects)
- Quiet mode enabled (
)-v q -
parameterizedBUILD_CONFIGURATION -
set/p:UseAppHost=false - Entry point references correct DLL
- .dockerignore excludes build artifacts
- Build scripts have version validation
-
copied if exists (ABP projects)common.props
Common Issues & Solutions
| Issue | Cause | Solution |
|---|---|---|
| "Could not find project or directory" | Missing flag | Add to all COPY commands |
| "Project reference could not be resolved" | Wrong project copy order | Order by dependencies (Domain → API) |
| Build fails in Alpine SDK | Package compatibility | Use full SDK: |
| Large image size (>200MB) | Not using Alpine runtime | Use |
| Cache not utilized | Wrong layer order | Publish stable layers first (Domain before API) |
| Build script fails | No version argument | Script validates argument existence |
| Missing common.props | ABP Framework project | Copy before project files |
| Slow rebuilds | Single publish approach | Switch to progressive publishing |
Build Commands Reference
Development Build
# Quick build for testing docker build -t myproject:dev . # Build with debug configuration docker build --build-arg BUILD_CONFIGURATION=Debug -t myproject:debug .
Production Build
# Using build script (recommended) ./build.sh 1.0.0 # Manual build with version docker build -t mycompany/myproject:1.0.0 -t mycompany/myproject:latest .
Testing the Image
# Run container docker run -d -p 8080:8080 --name myproject-test myproject:1.0.0 # Check health curl http://localhost:8080/health # View logs docker logs -f myproject-test # Inspect image size docker images myproject:1.0.0 # Stop and remove docker stop myproject-test && docker rm myproject-test
CI/CD Integration
# Build with commit SHA docker build -t myproject:${GITHUB_SHA} . # Multi-platform build docker buildx build --platform linux/amd64,linux/arm64 -t myproject:1.0.0 .
Performance Metrics
Typical improvements with this skill:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Image size | 450MB | 120MB | 73% smaller |
| Build time (full) | 180s | 200s | +20s (one-time cost) |
| Build time (cached) | 180s | 15s | 92% faster |
| Layer reuse | 30% | 85% | 2.8x better caching |
Note: Progressive publishing adds ~20s to initial build but saves 90%+ on subsequent builds
Best Practices Applied
- BuildKit features -
flag for automatic path preservation--parents - Layer optimization - Progressive publishing by dependency order
- Minimal images - Alpine sdk (sdk:9.0-alpine)
- Compatible builds - Full SDK or runtime avoids Alpine musl runtime issues.
- Security - Non-root user, specific tags, minimal attack surface
- Build efficiency - Quiet mode, ARG parameterization
- Caching strategy - Copy .csproj before source, order by stability
- Version control - Build scripts with validation and tagging
- ABP support - Handles common.props and framework patterns
- Production ready - UseAppHost=false, proper entry points
Usage Examples
Simple API:
Containerize my .NET 9 Web API project with Models library
ABP Framework:
Create Docker setup for my ABP Framework solution with HttpApi.Host
Clean Architecture:
Generate optimized Dockerfile for my Clean Architecture DDD solution with 6 projects
Optimization:
My Docker builds are slow, optimize the existing Dockerfile for better caching