MCPInstall

dotnet-containers

Containerizing .NET apps. Multi-stage Dockerfiles, SDK container publish (.NET 8+), rootless.

Installs
87
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-containers
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads dotnet-containers automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

dotnet-containers

Best practices for containerizing .NET applications. Covers multi-stage Dockerfile patterns, the dotnet publish container image feature (.NET 8+), rootless container configuration, optimized layer caching, and container health checks.

Out of scope: DI container mechanics and service lifetimes -- see [skill:dotnet-csharp-dependency-injection]. Kubernetes deployment manifests and Docker Compose orchestration are covered in [skill:dotnet-container-deployment]. CI/CD pipeline integration for building and pushing images -- see [skill:dotnet-gha-publish] and [skill:dotnet-ado-publish]. Testing containerized applications -- see [skill:dotnet-integration-testing] for Testcontainers patterns.

Cross-references: [skill:dotnet-observability] for health check patterns, [skill:dotnet-container-deployment] for deploying containers to Kubernetes and local dev with Compose, [skill:dotnet-artifacts-output] for Dockerfile path adjustments when using centralized build output layout.


Multi-Stage Dockerfiles

Multi-stage builds separate the build environment from the runtime environment, producing minimal final images.

Standard Multi-Stage Pattern

# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Copy project files first for layer caching
COPY ["src/MyApi/MyApi.csproj", "src/MyApi/"]
COPY ["src/MyApi.Core/MyApi.Core.csproj", "src/MyApi.Core/"]
COPY ["Directory.Build.props", "."]
COPY ["Directory.Packages.props", "."]
RUN dotnet restore "src/MyApi/MyApi.csproj"

# Copy everything else and build
COPY . .
WORKDIR "/src/src/MyApi"
RUN dotnet publish -c Release -o /app/publish --no-restore

# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
EXPOSE 8080

COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]

Layer Caching Strategy

Order COPY instructions from least-frequently-changed to most-frequently-changed:

  1. Project files and props -- change only when dependencies change
  2. dotnet restore -- cached until project files change
  3. Source code -- changes with every build
  4. dotnet publish -- runs only when source or restore layer changes
# Good: restore layer is cached when only source changes
COPY ["src/MyApi/MyApi.csproj", "src/MyApi/"]
RUN dotnet restore
COPY . .
RUN dotnet publish

# Bad: restore runs on every source change
COPY . .
RUN dotnet restore
RUN dotnet publish

Solution-Level Restore

For multi-project solutions, copy all .csproj files and the solution file to enable a single restore:

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Copy solution and all project files for restore caching
COPY ["MyApp.sln", "."]
COPY ["Directory.Build.props", "."]
COPY ["Directory.Packages.props", "."]
COPY ["src/MyApi/MyApi.csproj", "src/MyApi/"]
COPY ["src/MyApi.Core/MyApi.Core.csproj", "src/MyApi.Core/"]
COPY ["src/MyApi.Infrastructure/MyApi.Infrastructure.csproj", "src/MyApi.Infrastructure/"]
RUN dotnet restore

COPY . .
RUN dotnet publish "src/MyApi/MyApi.csproj" -c Release -o /app/publish --no-restore

dotnet publish Container Images (.NET 8+)

Starting with .NET 8, dotnet publish can produce OCI container images directly without a Dockerfile. This uses the Microsoft.NET.Build.Containers SDK (included in the .NET SDK).

Basic Usage

# Publish as a container image to local Docker daemon
dotnet publish --os linux --arch x64 /t:PublishContainer

# Publish to a remote registry
dotnet publish --os linux --arch x64 /t:PublishContainer \
  -p:ContainerRegistry=ghcr.io \
  -p:ContainerRepository=myorg/myapi

MSBuild Configuration

Configure container properties in the .csproj:

<PropertyGroup>
  <ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:10.0</ContainerBaseImage>
  <ContainerImageName>myapi</ContainerImageName>
  <ContainerImageTag>$(Version)</ContainerImageTag>
</PropertyGroup>

<ItemGroup>
  <ContainerPort Include="8080" Type="tcp" />
</ItemGroup>

Advanced Configuration

<PropertyGroup>
  <!-- Use chiseled (distroless) base image for smaller attack surface -->
  <ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled</ContainerBaseImage>

  <!-- Run as non-root user (default for chiseled images) -->
  <ContainerUser>app</ContainerUser>
</PropertyGroup>

<ItemGroup>
  <!-- Environment variables -->
  <ContainerEnvironmentVariable Include="ASPNETCORE_URLS" Value="http://+:8080" />
  <ContainerEnvironmentVariable Include="DOTNET_RUNNING_IN_CONTAINER" Value="true" />

  <!-- Labels -->
  <ContainerLabel Include="org.opencontainers.image.source" Value="https://github.com/myorg/myapi" />
</ItemGroup>

When to Use dotnet publish vs Dockerfile

ScenarioRecommendation
Simple single-project APIdotnet publish /t:PublishContainer -- less boilerplate
Multi-stage build with native dependenciesDockerfile -- full control over build environment
Need to install OS packages (e.g., libgdiplus)Dockerfile -- RUN apt-get install not available in SDK publish
CI/CD with complex build stepsDockerfile -- explicit, reproducible
Quick local container testingdotnet publish /t:PublishContainer -- fastest iteration

Base Image Selection

Official .NET Container Images

ImageUse CaseSize
mcr.microsoft.com/dotnet/aspnet:10.0ASP.NET Core apps (Ubuntu)~220 MB
mcr.microsoft.com/dotnet/aspnet:10.0-alpineASP.NET Core apps (Alpine, smaller)~110 MB
mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseledDistroless (no shell, no package manager)~110 MB
mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extraChiseled + globalization + time zones~130 MB
mcr.microsoft.com/dotnet/runtime:10.0Console apps, worker services~190 MB
mcr.microsoft.com/dotnet/runtime-deps:10.0Self-contained/AOT apps (runtime not needed)~30 MB

Choosing a Base Image

  • Default: Use aspnet for web apps, runtime for worker services
  • Minimal footprint: Use chiseled variants (no shell, no root user, no package manager)
  • Globalization needed: Use chiseled-extra if your app uses culture-specific formatting or time zones
  • Self-contained or AOT: Use runtime-deps -- the runtime is bundled in your app
  • Alpine: Smaller than Ubuntu but uses musl libc; test for compatibility with native dependencies

Rootless Containers

Running containers as non-root reduces the attack surface. .NET 8+ chiseled images run as non-root by default.

Non-Root with Standard Images

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app

# Create non-root user and switch to it
RUN adduser --disabled-password --gecos "" --uid 1001 appuser
USER appuser

COPY --from=build --chown=appuser:appuser /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]

Non-Root with Chiseled Images

Chiseled images include a pre-configured app user (UID 1654). No additional configuration needed:

FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS runtime
WORKDIR /app
# Already runs as non-root 'app' user (UID 1654)

COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]

Port Configuration

Non-root users cannot bind to ports below 1024. ASP.NET Core defaults to port 8080 in containers (set via ASPNETCORE_HTTP_PORTS):

# Default in .NET 8+ container images -- no explicit config needed
# ASPNETCORE_HTTP_PORTS=8080

# If you need a different port:
ENV ASPNETCORE_HTTP_PORTS=5000
EXPOSE 5000

Container Health Checks

Health checks allow container runtimes to monitor application readiness. The application-level health check endpoints (see [skill:dotnet-observability]) are consumed by Docker and Kubernetes probes.

Docker HEALTHCHECK

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app

# Health check using curl (not available in chiseled images)
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:8080/health/live || exit 1

COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]

For chiseled images (no curl), use a dedicated health check binary or rely on orchestrator-level probes (Kubernetes httpGet, Docker Compose test):

FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS runtime
WORKDIR /app

# No HEALTHCHECK directive -- use orchestrator probes instead
# See [skill:dotnet-container-deployment] for Kubernetes probe configuration

COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]

Health Check Endpoints

Register health check endpoints in your application (see [skill:dotnet-observability] for full guidance):

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
    .AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
    .AddNpgSql(
        builder.Configuration.GetConnectionString("DefaultConnection")!,
        name: "database",
        tags: ["ready"]);

var app = builder.Build();

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("live")
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});

Container Optimization

.dockerignore

Always include a .dockerignore to exclude unnecessary files from the build context:

**/.git
**/.vs
**/.vscode
**/bin
**/obj
**/node_modules
**/*.user
**/*.suo
**/Dockerfile*
**/docker-compose*
**/.dockerignore
**/README.md
**/LICENSE

Globalization and Time Zones

If your app needs globalization support (culture-specific formatting, time zones), configure ICU:

# Option 1: Use the chiseled-extra image (includes ICU + tzdata)
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra

# Option 2: Disable globalization for smaller images (if not needed)
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true

Memory Limits

Configure .NET to respect container memory limits:

# .NET automatically detects container memory limits and adjusts GC heap size.
# Override only if needed:
ENV DOTNET_GCHeapHardLimit=0x10000000  # 256 MB hard limit

.NET automatically reads cgroup memory limits. The GC adjusts its heap size to stay within the container memory budget. Avoid setting DOTNET_GCHeapHardLimit unless you have a specific reason.

ReadOnlyRootFilesystem

For defense-in-depth, run with a read-only root filesystem. Ensure writable paths for temp files:

ENV DOTNET_EnableDiagnostics=0
# Or mount a tmpfs at /tmp for diagnostics support

Key Principles

  • Use multi-stage builds -- keep build tools out of the final image
  • Order COPY for layer caching -- project files and restore before source code
  • Prefer chiseled images for production -- no shell, no root, minimal attack surface
  • Use dotnet publish /t:PublishContainer for simple projects -- skip Dockerfile boilerplate
  • Run as non-root -- use USER directive or chiseled images (non-root by default)
  • Set health check endpoints -- enable orchestrators to monitor application state (see [skill:dotnet-observability])
  • Include .dockerignore -- keep build context small and exclude secrets

Agent Gotchas

  1. Do not use mcr.microsoft.com/dotnet/sdk as the final image -- SDK images are 800+ MB and include build tools. Always use aspnet, runtime, or runtime-deps for the final stage.
  2. Do not hardcode image tags to a patch version (e.g., 10.0.1) -- use 10.0 to receive security patches. Pin to patch versions only if you have a specific compatibility requirement.
  3. Do not use HEALTHCHECK with chiseled images -- chiseled images have no curl or shell. Use orchestrator-level probes (Kubernetes httpGet, Docker Compose test) instead.
  4. Do not forget --no-restore on dotnet publish after a separate dotnet restore step -- without it, restore runs again and breaks layer caching.
  5. Do not bind to ports below 1024 in non-root containers -- .NET defaults to port 8080 in container images. If you override ASPNETCORE_HTTP_PORTS, ensure the port is >= 1024.
  6. Do not omit .dockerignore -- without it, the build context includes .git, bin/obj, and potentially secrets, increasing build time and image size.

References

FAQ

What does the dotnet-containers skill do?

Containerizing .NET apps. Multi-stage Dockerfiles, SDK container publish (.NET 8+), rootless.

How do I install dotnet-containers?

Run npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-containers in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/dotnet-containers/ (personal) or .claude/skills/dotnet-containers/ (project).

Which AI agents can use dotnet-containers?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is dotnet-containers safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains dotnet-containers?

It is published by wshaddix in wshaddix/dotnet-skills. It has 87 recorded installs.

More from wshaddix/dotnet-skills

View all →

dotnet-xunit

wshaddix/dotnet-skills

Writing xUnit tests. v3 Fact/Theory, fixtures, parallelism, IAsyncLifetime, v2 compatibility.

Backend & APIs613 installs

dotnet-testing-strategy

wshaddix/dotnet-skills

Deciding how to test .NET code. Unit vs integration vs E2E decision tree, test doubles.

Testing & QA610 installs

modern-csharp-coding-standards

wshaddix/dotnet-skills

Write modern, high-performance C code using records, pattern matching, value objects, async/await, Span /Memory , and best-practice API design patterns. Emphasizes functional-style programming with C 12+ features. Use when writing new C code or refactoring existing code, designing public APIs for libraries or services, optimizing performance-critical code paths, or building async/await-heavy applications.

csharp-wolverinefx

wshaddix/dotnet-skills

Build .NET applications with WolverineFX for messaging, HTTP services, and event sourcing. Use when implementing command handlers, message handlers, HTTP endpoints with WolverineFx.HTTP, transactional outbox patterns, event sourcing with Marten, CQRS architectures, cascading messages, batch message processing, or configuring transports like RabbitMQ, Azure Service Bus, or Amazon SQS.

Backend & APIs570 installs

testcontainers

wshaddix/dotnet-skills

Patterns for using Testcontainers in .NET integration tests to spin up real dependencies like databases and message queues. Use when writing integration tests that require real databases, testing with message brokers like RabbitMQ or Kafka, or isolating test dependencies with Docker containers.

Testing & QA565 installs

Dependency injection patterns and best practices using Microsoft.Extensions.DependencyInjection for .NET applications. Use when configuring DI containers in .NET, choosing between service lifetimes (Singleton, Scoped, Transient), or implementing decorator patterns and service interception.

Security549 installs

Popular backend & apis skills

View category →

find-skills

vercel-labs/skills

Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.

OfficialBackend & APIs4.1M installs

entra-app-registration

microsoft/azure-skills

Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.

OfficialBackend & APIs566.7K installs

This skill should be used when the user wants to "write agent code", "build an agent with ADK", "add a tool", "create a callback", "define an agent", "use state management" — in a project that needs ADK (Agent Development Kit) API patterns and code examples. It provides a quick reference for agent types, tool definitions, orchestration patterns, callbacks, state management, the graph Workflow API, and reference recipes to study. Do NOT use for scaffolding (use google-agents-cli-scaffold) or deployment (use google-agents-cli-deploy).

Backend & APIs514.8K installs

embedded-captions

heygen-com/hyperframes

Add captions or subtitles to an existing single-subject talking-head video without editing the footage. Use for plain verbatim captions, cinematic captions embedded behind the subject, VFX captions, “炸/特效/酷炫字幕,” or a named identity from the 35-style catalog. Route by visual identity, not by backend engine. The quiet anchor rail is the default; embed every word only when the user explicitly wants a fully cinematic treatment. The workflow runs locally end to end, including transcription and subject matting; split multi-shot footage before applying it.

Backend & APIs453.3K installs

entra-agent-id

microsoft/azure-skills

Provision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmipath, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmipath token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registration (use entra-app-registration), Microsoft Foundry agent authoring (use microsoft-foundry).

OfficialBackend & APIs336.6K installs

neon

neondatabase/agent-skills

Overview of Neon, a complete set of cloud backend primitives around Lakebase Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or MCP server, and follow the branch-first workflow. Use when building an app or backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child skill neon-postgres wins for an existing DATABASEURL, SQL, schema, inspect, or search. Child skill neon-auth wins for login, users, sessions, identity routing, and Managed Better Auth setup. Child skill neon-realtime wins for live queries in reactive apps and agents. Also use for object storage, S3, buckets, serverless functions, function triggers, cron, AI gateway, LLM calls, realtime, logs, Loki, Grafana, observability, postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.

Backend & APIs237.1K installs