Dockerizing ASP.NET Core (Production)
On this page
Production incident
Your container works locally, but in Kubernetes it crash-loops. It binds to localhost only. Or it runs as root and a supply-chain audit flags it. Or you ship a 1.2GB image, deployments take forever, and autoscaling is slow. Another incident: you trimmed the image so hard that diagnostic tools are missing during a live outage. Root cause: containerization treated as packaging, not an operational artifact.
Symptoms
- CrashLoopBackOff after deploy due to wrong ports/bindings.
- Slow deployments and slow scale-out due to huge images.
- Security scanners flag root user and writable filesystem issues.
- No ability to collect diagnostics in production images.
Root causes
- App binds to wrong URL (localhost) or wrong port assumptions.
- Single-stage builds that include SDK and build tools.
- Running as root by default.
- No health endpoints and no readiness assumptions in orchestration.
Diagnosis
# Check Dockerfile and runtime binding config grep -R "FROM mcr\.microsoft\.com/dotnet" -n Dockerfile* grep -R "ASPNETCORE_URLS\|DOTNET_URLS\|EXPOSE" -n . grep -R "USER " -n Dockerfile*
Anti-pattern
- Shipping SDK image to production runtime.
- Running as root and writing anywhere in the filesystem.
- Relying on implicit ports and environment defaults.
Correct pattern
- Multi-stage builds: build with SDK, run with runtime image.
- Non-root user, minimal filesystem permissions.
- Explicit ports and explicit binding (0.0.0.0).
- Keep diagnostics story: either include minimal tools or provide a separate debug image strategy.
Dockerfile baseline (multi-stage, non-root)
# Build stage FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY . . RUN dotnet restore RUN dotnet publish -c Release -o /out # Runtime stage FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime WORKDIR /app # Non-root (exact user setup depends on base image) # If base image supports it, create a user; otherwise use a predefined one. USER app ENV ASPNETCORE_URLS=http://0.0.0.0:8080 EXPOSE 8080 COPY --from=build /out ./ ENTRYPOINT ["dotnet","MyApp.dll"]
Operational hardening
- Set resource requests/limits to avoid noisy neighbor issues.
- Configure graceful shutdown time budgets (terminationGracePeriod) and app drain behavior.
- Use readiness probes tied to real dependency readiness (not just process alive).
Security and performance impact
- Security: non-root containers reduce blast radius. Smaller images reduce supply-chain surface.
- Performance: smaller images and fewer layers speed up deploys and scale-outs.
Operational notes
- Monitoring: image pull time, startup time, crash loops, readiness failure reasons.
- Rollback: keep last-known-good image tags and avoid mutable tags (latest).
Checklist
- Multi-stage Dockerfile (SDK build, runtime image).
- Runs as non-root with minimal permissions.
- Explicit bind address/port and consistent EXPOSE.
- Image size is controlled without removing critical diagnostics.
- Immutable tags and fast rollback exist.