container-publish

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Container Publishing (No Dockerfile)

容器发布(无需Dockerfile)

Core Principles

核心原则

  1. No Dockerfile needed — The .NET 10 SDK builds OCI-compliant container images directly from
    dotnet publish /t:PublishContainer
    . No Dockerfile to write or maintain.
  2. Chiseled images for production — Use
    noble-chiseled
    base images: no shell, no package manager, 7 Linux components vs 100+. Smallest attack surface.
  3. Non-root by default — .NET 10 container images run as the
    app
    user automatically. Never override to root in production.
  4. Configuration in the .csproj — All container settings are MSBuild properties, versioned with your project. No separate files to drift.
  1. 无需编写Dockerfile — .NET 10 SDK可直接通过
    dotnet publish /t:PublishContainer
    构建符合OCI标准的容器镜像,无需编写或维护Dockerfile。
  2. 面向生产的chiseled镜像 — 使用
    noble-chiseled
    基础镜像:无Shell、无包管理器,仅包含7个Linux组件(常规镜像包含100+),攻击面最小。
  3. 默认以非root用户运行 — .NET 10容器镜像自动以
    app
    用户运行,生产环境中绝不要切换为root用户。
  4. 在.csproj中配置 — 所有容器设置均为MSBuild属性,与项目版本一同管理,不会出现配置文件不一致的问题。

Patterns

实践模式

Minimal Container Publish

极简容器发布

No project file changes needed. Just publish:
bash
dotnet publish /t:PublishContainer --os linux --arch x64
This creates a container image in your local Docker daemon using the default
aspnet:10.0
base image.
无需修改项目文件,直接执行发布命令:
bash
dotnet publish /t:PublishContainer --os linux --arch x64
此命令会使用默认的
aspnet:10.0
基础镜像,在本地Docker守护进程中创建容器镜像。

Production-Ready .csproj Configuration

生产就绪的.csproj配置

xml
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ContainerRepository>mycompany/myapp-api</ContainerRepository>
    <ContainerFamily>noble-chiseled</ContainerFamily>
  </PropertyGroup>

  <ItemGroup>
    <ContainerPort Include="8080" Type="tcp" />
    <ContainerEnvironmentVariable Include="ASPNETCORE_HTTP_PORTS" Value="8080" />
    <ContainerEnvironmentVariable Include="DOTNET_EnableDiagnostics" Value="0" />
    <ContainerLabel Include="org.opencontainers.image.vendor" Value="MyCompany" />
  </ItemGroup>

</Project>
xml
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ContainerRepository>mycompany/myapp-api</ContainerRepository>
    <ContainerFamily>noble-chiseled</ContainerFamily>
  </PropertyGroup>

  <ItemGroup>
    <ContainerPort Include="8080" Type="tcp" />
    <ContainerEnvironmentVariable Include="ASPNETCORE_HTTP_PORTS" Value="8080" />
    <ContainerEnvironmentVariable Include="DOTNET_EnableDiagnostics" Value="0" />
    <ContainerLabel Include="org.opencontainers.image.vendor" Value="MyCompany" />
  </ItemGroup>

</Project>

Publishing to a Registry

发布到镜像仓库

Authenticate with
docker login
first, then specify the registry:
bash
undefined
先通过
docker login
完成认证,再指定镜像仓库:
bash
undefined

GitHub Container Registry

GitHub Container Registry

docker login ghcr.io dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerRegistry=ghcr.io
-p ContainerImageTag=1.0.0
docker login ghcr.io dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerRegistry=ghcr.io
-p ContainerImageTag=1.0.0

Azure Container Registry

Azure Container Registry

az acr login --name myregistry dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerRegistry=myregistry.azurecr.io
az acr login --name myregistry dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerRegistry=myregistry.azurecr.io

Docker Hub (requires username prefix in repository)

Docker Hub(仓库名需包含用户名前缀)

dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerRegistry=docker.io
-p ContainerRepository=myuser/myapp
undefined
dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerRegistry=docker.io
-p ContainerRepository=myuser/myapp
undefined

Multi-Architecture Images

多架构镜像

Build images for multiple platforms with a single publish:
xml
<PropertyGroup>
    <RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers>
    <ContainerRuntimeIdentifiers>linux-x64;linux-arm64</ContainerRuntimeIdentifiers>
</PropertyGroup>
bash
dotnet publish /t:PublishContainer
This produces an OCI Image Index — registries serve the correct architecture automatically.
通过一次发布命令构建多平台镜像:
xml
<PropertyGroup>
    <RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers>
    <ContainerRuntimeIdentifiers>linux-x64;linux-arm64</ContainerRuntimeIdentifiers>
</PropertyGroup>
bash
dotnet publish /t:PublishContainer
此命令会生成OCI镜像索引——镜像仓库会自动为不同架构提供对应的镜像。

Multiple Tags

多标签设置

bash
undefined
bash
undefined

Bash — note the quoting for semicolons

Bash — 注意分号需要加引号

dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerImageTags='"1.0.0;latest"'

Or in the project file:

```xml
<ContainerImageTags>1.0.0;latest</ContainerImageTags>
dotnet publish /t:PublishContainer --os linux --arch x64
-p ContainerImageTags='"1.0.0;latest"'

也可在项目文件中配置:

```xml
<ContainerImageTags>1.0.0;latest</ContainerImageTags>

Save as Tarball (No Docker Required)

保存为Tar包(无需Docker)

No container runtime needed on the build machine. Useful for CI scanning:
bash
dotnet publish /t:PublishContainer --os linux --arch x64 \
    -p ContainerArchiveOutputPath=./images/myapp.tar.gz
构建机器无需安装容器运行时,适用于CI扫描场景:
bash
dotnet publish /t:PublishContainer --os linux --arch x64 \
    -p ContainerArchiveOutputPath=./images/myapp.tar.gz

Scan with Trivy before pushing

推送前用Trivy扫描

trivy image --input ./images/myapp.tar.gz
undefined
trivy image --input ./images/myapp.tar.gz
undefined

Chiseled Image Variants

Chiseled镜像变体

ContainerFamilyUse CaseShellSize
(default)General purpose (Debian)Yes~220 MB
noble-chiseled
Production (no shell)No~110 MB
noble-chiseled-extra
Production with localization (ICU)No~120 MB
alpine
Small size, has shellYes~112 MB
xml
<!-- Standard chiseled (InvariantGlobalization=true) -->
<ContainerFamily>noble-chiseled</ContainerFamily>

<!-- Chiseled with ICU for localization -->
<ContainerFamily>noble-chiseled-extra</ContainerFamily>
For Native AOT, the SDK auto-selects
chiseled-aot
:
xml
<PublishAot>true</PublishAot>
<!-- SDK picks runtime-deps:10.0-noble-chiseled-aot automatically -->
ContainerFamily使用场景是否包含Shell大小
(默认)通用场景(Debian)~220 MB
noble-chiseled
生产环境(无Shell)~110 MB
noble-chiseled-extra
带本地化支持的生产环境(ICU)~120 MB
alpine
轻量场景,包含Shell~112 MB
xml
<!-- 标准chiseled镜像(启用InvariantGlobalization=true) -->
<ContainerFamily>noble-chiseled</ContainerFamily>

<!-- 带ICU本地化支持的chiseled镜像 -->
<ContainerFamily>noble-chiseled-extra</ContainerFamily>
对于Native AOT,SDK会自动选择
chiseled-aot
xml
<PublishAot>true</PublishAot>
<!-- SDK会自动选择runtime-deps:10.0-noble-chiseled-aot作为基础镜像 -->

CI/CD with GitHub Actions

GitHub Actions CI/CD配置

yaml
jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-dotnet@v5
        with:
          dotnet-version: '10.0.x'
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - run: |
          dotnet publish src/MyApp.Api/MyApp.Api.csproj \
            /t:PublishContainer --os linux --arch x64 \
            -p ContainerRegistry=ghcr.io \
            -p ContainerRepository=${{ github.repository_owner }}/myapp \
            -p ContainerImageTag=${{ github.sha }}
yaml
jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-dotnet@v5
        with:
          dotnet-version: '10.0.x'
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - run: |
          dotnet publish src/MyApp.Api/MyApp.Api.csproj \
            /t:PublishContainer --os linux --arch x64 \
            -p ContainerRegistry=ghcr.io \
            -p ContainerRepository=${{ github.repository_owner }}/myapp \
            -p ContainerImageTag=${{ github.sha }}

Anti-patterns

反模式

Don't Use the Deprecated Property Names

不要使用已废弃的属性名

xml
<!-- BAD — ContainerImageName is deprecated -->
<ContainerImageName>myapp</ContainerImageName>

<!-- GOOD — use ContainerRepository -->
<ContainerRepository>myapp</ContainerRepository>
xml
<!-- 错误用法 — ContainerImageName已废弃 -->
<ContainerImageName>myapp</ContainerImageName>

<!-- 正确用法 — 使用ContainerRepository -->
<ContainerRepository>myapp</ContainerRepository>

Don't Use PublishProfile=DefaultContainer

不要使用PublishProfile=DefaultContainer

bash
undefined
bash
undefined

BAD — old approach, inconsistent across project types

错误用法 — 旧方案,不同项目类型表现不一致

dotnet publish -p:PublishProfile=DefaultContainer
dotnet publish -p:PublishProfile=DefaultContainer

GOOD — use the MSBuild target directly

正确用法 — 直接使用MSBuild目标

dotnet publish /t:PublishContainer
undefined
dotnet publish /t:PublishContainer
undefined

Don't Forget to Target Linux

不要忘记指定Linux目标

bash
undefined
bash
undefined

BAD on Windows — may produce a Windows container

Windows环境下的错误用法 — 可能生成Windows容器

dotnet publish /t:PublishContainer
dotnet publish /t:PublishContainer

GOOD — explicitly target Linux

正确用法 — 明确指定Linux目标

dotnet publish /t:PublishContainer --os linux --arch x64
undefined
dotnet publish /t:PublishContainer --os linux --arch x64
undefined

Don't Skip Authentication Before Push

推送前不要跳过认证

bash
undefined
bash
undefined

BAD — fails with CONTAINER1013 error

错误用法 — 会触发CONTAINER1013错误

dotnet publish /t:PublishContainer -p ContainerRegistry=ghcr.io
dotnet publish /t:PublishContainer -p ContainerRegistry=ghcr.io

GOOD — authenticate first

正确用法 — 先完成认证

docker login ghcr.io dotnet publish /t:PublishContainer -p ContainerRegistry=ghcr.io
undefined
docker login ghcr.io dotnet publish /t:PublishContainer -p ContainerRegistry=ghcr.io
undefined

Don't Use SDK Publishing When You Need OS Packages

需要安装系统包时不要使用SDK发布

xml
<!-- BAD — SDK container publish cannot run apt-get or install native packages -->
<!-- There is no RUN equivalent -->

<!-- GOOD — create a custom base image with a Dockerfile first, then reference it -->
<ContainerBaseImage>myregistry/custom-base:1.0</ContainerBaseImage>
xml
<!-- 错误用法 — SDK容器发布无法执行apt-get或安装原生包 -->
<!-- 没有对应的RUN指令 -->

<!-- 正确用法 — 先用Dockerfile创建自定义基础镜像,再引用它 -->
<ContainerBaseImage>myregistry/custom-base:1.0</ContainerBaseImage>

Decision Guide

决策指南

ScenarioRecommendation
Standard ASP.NET Core APISDK container publishing with
noble-chiseled
Worker service / console appSDK container publishing (native .NET 10 support)
Needs native OS packagesDockerfile (or custom base image + SDK publishing)
Azure FunctionsDockerfile (not supported by SDK publishing)
CI without Docker daemonTarball output with
ContainerArchiveOutputPath
Multi-arch deployment (x64 + arm64)
ContainerRuntimeIdentifiers
property
Production image size
noble-chiseled
(~110 MB) or Native AOT (~10 MB)
Local development
dotnet publish /t:PublishContainer --os linux --arch x64
Registry push
ContainerRegistry
+
docker login
场景推荐方案
标准ASP.NET Core API使用SDK容器发布 +
noble-chiseled
镜像
Worker服务/控制台应用使用SDK容器发布(.NET 10原生支持)
需要安装原生系统包使用Dockerfile(或自定义基础镜像 + SDK发布)
Azure Functions使用Dockerfile(SDK发布暂不支持)
无Docker守护进程的CI环境通过
ContainerArchiveOutputPath
输出Tar包
多架构部署(x64 + arm64)使用
ContainerRuntimeIdentifiers
属性
生产镜像轻量化使用
noble-chiseled
(约110 MB)或Native AOT(约10 MB)
本地开发执行
dotnet publish /t:PublishContainer --os linux --arch x64
推送至镜像仓库使用
ContainerRegistry
+
docker login