✓
Home›Tutorials›.NET & ASP.NET Core›ASP.NET Core 10 Docker Tutorial: Containerize Your Tested Minimal API
.NET & ASP.NET Core3 min read

ASP.NET Core 10 Docker Tutorial: Containerize Your Tested Minimal API

Containerize a tested ASP.NET Core 10 Minimal API with a multi-stage Dockerfile, a lean runtime image, safe context rules, and local validation.

ASP.NET Core 10 Docker Tutorial: Containerize Your Tested Minimal API

Docker is the next delivery boundary for the API you tested in the previous lesson. A container is not a substitute for tests, configuration, or secure operations; it is a repeatable packaging unit. In this lesson, you will build a multi-stage image for an ASP.NET Core 10 Minimal API, run it locally, and validate the same endpoint from outside the container.

Prerequisites

Follow the full sequence on the .NET and ASP.NET Core learning track.

Working outcome

You will produce an image with build tools left behind in the build stage, a runtime-only final stage, an explicit port, and a non-root application user. The final image can answer a health request locally.

Use a multi-stage Dockerfile

Copy project files before the remaining source files so dependency restore can be cached independently. Name the stages: it makes later changes safer than relying on a numeric index. Use the SDK only to restore, build, and publish; copy the published output into the ASP.NET runtime image.

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

COPY ["Orders.Api/Orders.Api.csproj", "Orders.Api/"]
RUN dotnet restore "Orders.Api/Orders.Api.csproj"

COPY . .
WORKDIR "/src/Orders.Api"
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080

RUN adduser --disabled-password --gecos "" appuser
USER appuser
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "Orders.Api.dll"]

Keep the build context small

Docker sends the build context before it can execute instructions. Do not send build output, local secrets, IDE folders, test results, or source-control metadata unless a build genuinely needs them. Add a .dockerignore file beside the Dockerfile.

**/bin/
**/obj/
.git/
.vs/
TestResults/
*.user
appsettings.Development.json

Do not put production settings or secrets in this file because they should not live in the context in the first place. Supply runtime configuration using your deployment platform’s secret and configuration mechanisms.

Build, run, and validate

docker build --pull -t orders-api:local .
docker run --rm -p 8080:8080 --name orders-api orders-api:local
curl -i http://localhost:8080/health

The response should be the one your application intentionally exposes. In a second terminal, inspect logs and confirm the process runs as the configured non-root user if your base image and platform support the command. Keep your integration tests in CI: a successful image build only proves packaging, not endpoint behavior.

Common mistakes

  • Using an SDK image in production: it carries build tooling that the runtime does not require.
  • Copying the whole repository before restore: ordinary source edits invalidate the restore cache.
  • Publishing secrets into the image: image layers and registries are not a secret store.
  • Assuming EXPOSE publishes a port: map a host port at runtime or configure your orchestrator.
  • Running as root by habit: use the least privilege your application needs and test filesystem permissions.

Validation and production guidance

  1. Build from a clean checkout and confirm .dockerignore excludes local output.
  2. Run the container and exercise the tested health and authenticated endpoints with the expected environment configuration.
  3. Scan and regularly rebuild images so updated base-image fixes are incorporated.
  4. Pin a deliberate base-image strategy and record its provenance; test upgrades in CI.
  5. Set resource limits, read-only filesystem policy where compatible, health probes, and structured logs in the deployment manifest.

Docker’s multi-stage builds intentionally leave build tools and intermediate files out of the final stage. That separation improves distribution efficiency and reduces unnecessary runtime surface, but it does not replace patching or deployment policy.

Next lesson

Lesson 8 will deploy this container: configuration, registry, environment-specific settings, health checks, and a safe rollout. Continue only after the image builds reproducibly and your endpoint checks pass.

Sources