Docker becomes less intimidating when each instruction has one job. Start with the mental model, then read the Dockerfile as a build pipeline.
Work through the path
Open a card when you need it. Marking a lesson complete is optional and stays only in this browser.
1An image is a recipe; a container is a running instance
An image packages filesystem layers and metadata; a container is an isolated process created from that image.
Building an image does not start your application. Running a container creates a process with the image filesystem plus networking, environment and volume settings.
The same image can run in different environments with different configuration. Keep environment-specific values outside the image.
Try it
Build, run, stop and remove the container. Notice that the image remains available for another run.
# Build an image docker build -t orders-api:dev . # Map host port 8080 to container port 8080 docker run --rm -p 8080:8080 orders-api:dev
2Read a .NET Dockerfile line by line
A multi-stage Dockerfile uses the SDK to publish, then copies only the published app into a smaller runtime image.
The first stage needs compilers and NuGet tooling. The final stage needs only the ASP.NET runtime and published output.
Copy project files and restore before copying the rest of the source when the project layout allows it, so cache reuse survives normal code edits.
Try it
For each line, say whether it affects build dependencies, runtime behavior or both.
FROM mcr.microsoft.com/dotnet/sdk:8.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 --no-restore FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final WORKDIR /app EXPOSE 8080 ENV ASPNETCORE_HTTP_PORTS=8080 COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "Orders.Api.dll"]
3Keep configuration outside the image
Build once and configure at runtime through environment variables, secret mounts or the platform secret store.
An image can be promoted from test to production only when environment-specific values are injected at runtime. Never bake passwords, provider keys or private certificates into a Dockerfile layer.
ASP.NET Core maps environment variables using double underscores, so ConnectionStrings__Default can override a nested setting.
Try it
Replace the connection string with a placeholder and move the real value into your local secret mechanism or orchestrator.
docker run --rm \ -p 8080:8080 \ -e ASPNETCORE_ENVIRONMENT=Production \ -e ConnectionStrings__Default="Server=db;Database=Orders;..." \ orders-api:dev
4Use Compose to describe local dependencies
Compose gives a local .NET app a repeatable network, database, volume and health-check definition.
depends_on controls startup ordering but does not by itself mean a database is ready. A health check and application retry behavior make a local stack more realistic.
Keep local credentials clearly marked as development-only. Production needs a deliberate secret and backup strategy.
Try it
Add the file to a small API and watch database health before calling the API.
services:
api:
build: .
ports:
- "8080:8080"
environment:
ConnectionStrings__Default: Host=db;Database=orders;Username=app;Password=dev-only
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d orders"]
interval: 5s
retries: 10No lesson matches that phrase.
Related tools
Turn the concept into a working example with the browser-based tools.