LEARN BUILD GROW TOGETHER
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Home /Containerization /Docker & Containerization – Complete Developer Guide

Docker & Containerization – Complete Developer Guide

November 15, 2026 5 min read , , ,
Docker & Containerization: Complete Developer Learning Guide

Docker & Containerization

A practical, beginner-to-advanced developer guide to understanding containers, building reliable images, running multi-container applications, and shipping software consistently — with .NET 10 and C# 14 examples, while keeping the concepts useful for every developer.

01. Introduction to Containerization

Imagine that an ASP.NET Core API runs correctly on a developer's laptop but fails after deployment. The server may have a different runtime, missing native dependencies, a different configuration, or a conflicting software version. The application code may be correct, yet its execution environment is not equivalent.

Containerization packages an application and the files, runtime components, and configuration it needs into a portable image. A container is a running instance of that image, isolated from other processes by operating-system features.

Source applicationCode + dependenciesContainer imagePackaged templateVersioned artifactRunning containerIsolated process

Build an image once, then create containers from that image in different environments.

Important distinction: An image is the packaged template; a container is an instance created from it. A container is not a tiny virtual machine.

Docker is a popular platform for building, distributing, and running containers. The underlying container concepts are useful with other runtimes and platforms too.

02. Why Containers Matter

  • Consistency: package the runtime and application dependencies so development, testing, and production can use the same image.
  • Portability: run a compatible image on different container hosts without reinstalling application dependencies manually.
  • Isolation: processes have separated filesystem, process, and network views according to the runtime configuration.
  • Repeatability: build and deploy a known image version instead of copying files by hand.
  • Resource efficiency: containers share the host kernel rather than booting a separate guest operating system for each application.
  • Scaling: create additional instances when the application architecture and available infrastructure support them.

Containers do not automatically make software faster, secure, highly available, or scalable. Those outcomes depend on application design, resource limits, orchestration, monitoring, and operational practices.

03. Virtual Machines vs Containers

Virtual machinesContainersApp AGuest OSApp BGuest OSHypervisorHost operating system + hardwareApp AApp BApp CContainerContainerContainerContainer runtimeHost operating system + kernel

Simplified conceptual comparison. Containers share the host kernel; VMs run separate guest operating systems.

AspectVirtual machineContainer
Operating systemIncludes a guest OSShares the host kernel
StartupUsually slower because a VM boots an OSUsually starts faster as a process
FootprintGenerally largerGenerally smaller, depending on image
Isolation boundaryVirtualized hardware and guest OSOS-level isolation; configuration matters
Typical useDifferent operating systems, stronger VM boundary, legacy workloadsPortable application packaging and service workloads

Containers and VMs are complementary. A cloud VM or physical server often hosts a container runtime, and containers run on that host.

04. What Is Docker?

Docker is a platform and ecosystem used to build, distribute, and run container images. Developers commonly interact with it through the Docker CLI, Docker Engine, Docker Desktop, image registries, and Docker Compose.

Core terms

  • Docker Engine: the container runtime and API service that manages images, containers, networks, and volumes.
  • Docker CLI: the command-line client, usually invoked with docker.
  • Docker Desktop: a desktop application that provides a convenient local development environment on supported systems.
  • Dockerfile: a text file describing how to build an image.
  • Registry: a service for storing and distributing images, such as Docker Hub or a private registry.
  • Docker Compose: a tool for defining and running multi-container applications from a YAML file.
Licensing note: Docker Desktop has subscription terms for some business use cases. Check the current Docker terms for your organization. Docker Engine and Docker Desktop are not interchangeable names for the same licensing arrangement.

05. Docker Architecture

Docker CLIdocker build / runDocker EngineAPI + daemonManages resourcesImagesContainersNetworksVolumes

The CLI sends requests to the Engine API; the engine manages container resources.

When you run docker run nginx, the Docker client requests the engine to find or pull the image, create a container, configure its resources, and start its process. The engine may contact a registry when the image is not available locally.

06. Images, Containers and Registries

An image is a read-only, layered package. A container is a created and running (or stopped) instance with a writable layer and runtime configuration. A registry stores image versions so teams and deployment systems can retrieve them.

Registrymy-api:1.0.0Container AContainer BContainer CSame imageSeparate instances
TermMeaning
TagA human-readable reference such as catalog-api:1.0.0.
DigestA content-addressed identifier for an image manifest; useful for immutable references.
LayerA reusable filesystem change contributing to an image.
RegistryA repository service that stores and distributes images.

Tags can be moved to point to a different image. For repeatable production deployments, record the exact image digest or use controlled immutable tagging policies.

07. Install Docker and Verify Setup

For a Windows developer, Docker Desktop with the WSL 2 backend is a common local setup. macOS and Linux have their own supported installation paths. Follow the official installation instructions for your operating system and organization policy.

  1. Install or update the supported Docker Desktop release, or install Docker Engine on a supported Linux host.
  2. On Windows, enable the required virtualization features and configure WSL 2 when using that backend.
  3. Start Docker and wait until the engine is ready.
  4. Open a terminal and verify the client and server.
docker --version
docker version
docker info
docker run hello-world

docker --version reports the CLI version. docker version also attempts to contact the engine. If the client is present but the daemon is stopped or unreachable, the second command can fail.

Docker releases change frequently. Install the current stable release from the official Docker download page rather than copying a version number from a static tutorial.

08. Run Your First Container

Start a small web server container and publish its port to your computer:

docker run --name hello-web -d -p 8080:80 nginx:stable
docker ps

Open http://localhost:8080 in a browser. The mapping 8080:80 means host port 8080 forwards traffic to container port 80. The -d option runs the container in the background, and --name gives it a readable name.

docker stop hello-web
docker start hello-web
docker rm -f hello-web

The nginx:stable tag is used here as a teaching example. For controlled production builds, select and maintain an explicit image version and preferably record its digest.

09. Essential Docker Commands

CommandPurpose
docker imagesList local images.
docker psList running containers.
docker ps -aList all containers, including stopped ones.
docker pull image:tagDownload an image.
docker build -t name:tag .Build an image from the current build context.
docker runCreate and start a container.
docker logs containerRead container standard output and error.
docker exec -it container shOpen a shell if the image includes one.
docker inspect containerInspect configuration and runtime details.
docker statsView live resource usage.
docker rm containerRemove a stopped container.
docker rmi imageRemove an image not in use.
docker compose up -dCreate and start services defined in Compose.
docker compose downStop and remove Compose containers and networks.

Use docker compose down -v only when you intentionally want to remove the Compose-managed named volumes too. That can delete local database data.

10. Understand a Dockerfile

A Dockerfile is a set of instructions used by the builder to create an image. The following is a small example for a static site; a .NET application needs a build and runtime setup that we will create later.

# syntax=docker/dockerfile:1
FROM nginx:stable
COPY ./site/ /usr/share/nginx/html/
InstructionWhat it does
FROMSelects a base image and starts a build stage.
WORKDIRSets the working directory for following instructions.
COPYCopies files from the build context into the image.
RUNExecutes a command during image build.
ENVSets an environment variable in the image configuration.
EXPOSEDocuments the port the application is intended to listen on; it does not publish the port.
USERSelects the user for subsequent build/runtime instructions.
ENTRYPOINTDefines the main executable for the container.
CMDProvides default command or arguments, depending on ENTRYPOINT form.

Dockerfile instructions are evaluated in order. Each instruction can contribute to the resulting image and its cache behavior.

11. Build Context and .dockerignore

The build context is the set of files made available to the builder. In docker build -t catalog-api ., the final dot means the current directory is the context. Avoid sending unnecessary files such as local build output, Git history, secrets, and IDE folders.

Create a .dockerignore file at the context root:

**/bin/
**/obj/
.git/
.vs/
.vscode/
TestResults/
.env
.env.*
secrets/
*.user
*.suo
Dockerfile*
compose*.yaml
README.md

Adjust the patterns to your project. Do not exclude files that the Dockerfile needs. Never copy passwords, private keys, or production configuration into an image.

Security point: A secret copied into an image layer can remain recoverable even if a later layer deletes the file. Keep secrets out of the build context and use build/runtime secret mechanisms when required.

12. Build and Tag an Image

Assume the Dockerfile is in the current directory. Build an image and run it:

docker build -t catalog-api:1.0.0 .
docker image ls
docker run --name catalog-api -d -p 8080:8080 catalog-api:1.0.0

The name has a repository component (catalog-api) and a tag (1.0.0). A tag is a label, not a guarantee that the underlying image can never change.

To add another tag to the same local image:

docker tag catalog-api:1.0.0 registry.example.com/team/catalog-api:1.0.0

Push only after authenticating to a registry and confirming the target repository and access permissions.

13. Multi-Stage Builds for .NET 10

A .NET SDK image contains compilers and build tools. A production application usually does not need those tools at runtime. A multi-stage Dockerfile uses one stage to restore and publish the app, then copies only the published output into a smaller runtime stage.

Example project layout:

CatalogApi/
  CatalogApi.csproj
  Program.cs
  Dockerfile
  .dockerignore

Example Dockerfile for an ASP.NET Core Web API targeting .NET 10:

# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Copy the project file first to improve restore-layer caching.
COPY ["CatalogApi.csproj", "./"]
RUN dotnet restore "CatalogApi.csproj"

# Copy the remaining source and publish.
COPY . .
RUN dotnet publish "CatalogApi.csproj" \
    -c Release \
    -o /app/publish \
    --no-restore

# Runtime stage: no SDK or source tree is copied.
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app

# ASP.NET Core container convention: listen on port 8080.
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080

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

Build and run:

docker build -t catalog-api:1.0.0 .
docker run --rm -p 8080:8080 catalog-api:1.0.0

Browse to http://localhost:8080 if the API has a route or landing response configured. In a real solution with multiple projects, copy the relevant solution and project files before restore, and preserve their relative paths. Adjust the Dockerfile to the actual repository layout.

Why two stages? The SDK stage compiles and publishes the application. The final ASP.NET runtime stage contains the published application and runtime dependencies, reducing unnecessary build tools in the runtime image.

.NET 10 is the framework baseline for this guide. C# 14 is the corresponding language version available with the .NET 10 toolchain; Docker itself does not require a special C# syntax. Use language features where they improve application code, not merely to make a container example look newer.

14. Ports and Container Networking

An application inside a container must listen on an interface and port reachable from the container network. Docker port publishing creates a path from a host port to a container port.

Browserlocalhost:8080Docker hostContainer port 8080 ← Host port 8080

The mapping syntax is HOST_PORT:CONTAINER_PORT.

docker run --rm -p 8080:8080 catalog-api:1.0.0

EXPOSE 8080 in a Dockerfile is documentation metadata. It does not publish a port. The -p option publishes a port. Also ensure the ASP.NET Core process listens on a container-reachable address, not only on loopback.

When using Compose, services on the same Compose network can usually reach one another by service name and container port. For example, an API service can connect to a database service using host name db, not localhost.

15. Environment Configuration

Applications need environment-specific settings such as connection strings, feature flags, and external service endpoints. Keep the image environment-neutral and provide deployment configuration when the container starts.

For ASP.NET Core, environment variables can override configuration values. A nested key such as ConnectionStrings:Default is represented as:

ConnectionStrings__Default=Server=db;Database=CatalogDb;...

Example local run:

docker run --rm \
  -p 8080:8080 \
  -e ASPNETCORE_ENVIRONMENT=Development \
  -e Logging__LogLevel__Default=Information \
  catalog-api:1.0.0
Never bake secrets into an image or commit real credentials to source control. Use a secret manager or the deployment platform's protected secret mechanism for production. Environment variables are convenient configuration inputs, but they are not automatically a secure secret vault.

16. Volumes and Persistent Data

A container's writable layer is tied to the container lifecycle. If a database stores its files only in that layer, removing the container can remove the data. Volumes provide storage managed separately from the container.

Database containerSQL Server processNamed volumeDatabase files persist independently
docker volume create catalog-data

docker run -d \
  --name sql-local \
  -e ACCEPT_EULA=Y \
  -e MSSQL_SA_PASSWORD='Use-A-Local-Strong-Password!' \
  -p 1433:1433 \
  -v catalog-data:/var/opt/mssql \
  mcr.microsoft.com/mssql/server:2022-latest

This is a local learning example. Use a strong unique local password and do not reuse it in production. Confirm SQL Server container image requirements, platform support, licensing, and password policy in the official Microsoft documentation. Backups are still required; a volume is not a backup.

A bind mount maps a host path into a container, often useful for source code during development. A named volume is managed by Docker and is commonly used for persistent container data.

17. Docker Networks

Networks let containers communicate without exposing every service directly to the host. Compose creates a default project network automatically, and services can discover one another by service name.

docker network create app-network

docker run -d --name web --network app-network nginx:stable
docker run --rm -it --network app-network busybox sh

Inside the second container, tools that are available in the image can test DNS resolution or connectivity to web. The exact diagnostic commands depend on the image; minimal production images may not include a shell or network utilities.

Do not publish a database port to the host unless local tools need it. An API and database can communicate over a private Docker network without making the database port public.

18. Docker Compose

Docker Compose describes an application made of multiple services in a YAML file. It is especially useful for local development and integration testing: an API, database, cache, and supporting services can be started with one command.

Modern Compose uses the Compose Specification. A typical file is named compose.yaml:

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      ASPNETCORE_HTTP_PORTS: "8080"
      ASPNETCORE_ENVIRONMENT: Development
      ConnectionStrings__Default: "Server=db;Database=CatalogDb;User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True"
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "${DB_PASSWORD}"
    volumes:
      - catalog-data:/var/opt/mssql
    healthcheck:
      test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

volumes:
  catalog-data:

The SQL Server health-check command and tool path can differ by image release. Verify it against the exact SQL Server image you use; if the chosen image does not contain the expected client tool, provide a suitable health-check strategy or omit this example health check and handle readiness in the application.

Place a local .env file beside the Compose file for this learning setup:

DB_PASSWORD=Use-A-Unique-Local-Password!

Add .env to .gitignore. For production, inject secrets from a dedicated secret manager or platform mechanism rather than relying on a developer's local environment file.

docker compose up --build -d
docker compose ps
docker compose logs -f api
docker compose down
Data safety: docker compose down normally preserves named volumes. docker compose down -v removes them. Do not use the latter if you need the local database data.

19. Run an API with SQL Server

A common .NET development stack contains an ASP.NET Core Web API and SQL Server. Keep them in separate containers: the API container runs application code, while the database container owns database processes and persistent storage.

ClientBrowser / UIASP.NET Core API.NET 10 containerPort 8080SQL ServerPrivate networkAPI connects to host name db and the database container port

In a Compose network, the connection string should use the service name and container port:

Server=db,1433;Database=CatalogDb;
User Id=sa;Password=...;
TrustServerCertificate=True;

Do not use localhost as the database host from inside the API container. Inside a container, localhost refers to that same container. The Compose service name db resolves to the database container on the shared network.

For production, evaluate whether a managed database service is preferable to operating SQL Server in a container. Containerizing a database does not remove the responsibilities for backups, patching, availability, storage, monitoring, and licensing.

20. Health Checks and Dependencies

A container process can be running while the application is not ready to serve requests. A health check tests a meaningful condition and reports a health state. Compose can use health status to gate dependent service startup when configured with a supported condition.

For an API, a health endpoint might be implemented with ASP.NET Core health checks:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/health");
app.MapGet("/", () => Results.Ok("Catalog API is running"));

app.Run();

This is valid modern C# syntax and works with the .NET 10 project baseline. The health endpoint above checks the registered health checks; add database or downstream dependency checks only when the readiness contract requires them.

Health checks are not a substitute for retries, timeouts, graceful shutdown, or resilient connection handling. Applications should tolerate dependencies becoming temporarily unavailable after startup too.

21. Development Containers and Watch

Development and production have different needs. A developer may need the SDK, source files, debugging tools, and hot reload. A production image should usually contain only the published application and runtime requirements.

Docker Compose Watch can synchronize or rebuild parts of a development project when files change. It is a development workflow, not a production deployment strategy. Check the current Compose version and documentation for supported watch actions.

services:
  api:
    build:
      context: .
      target: build
    develop:
      watch:
        - action: sync
          path: .
          target: /src
          ignore:
            - bin/
            - obj/
        - action: rebuild
          path: CatalogApi.csproj

This is an illustrative configuration: a real development stage needs a suitable working directory, SDK image, and process command (for example, dotnet watch run). Do not copy a production Dockerfile target blindly; define a dedicated development stage when needed.

22. Logs, Debugging and Troubleshooting

Start with observable facts before rebuilding images or changing configuration:

docker ps -a
docker logs catalog-api
docker logs --tail 100 -f catalog-api
docker inspect catalog-api
docker stats
docker exec -it catalog-api sh
SymptomWhat to investigate
Container exits immediatelyRead logs, inspect exit code, check ENTRYPOINT and application startup errors.
Browser cannot connectCheck application listen address, container port, host port mapping, and firewall.
API cannot reach databaseCheck Compose network, service name, container port, credentials, and database readiness.
File changes are missingCheck build context, COPY instructions, bind mounts, and whether the image was rebuilt.
Permission deniedCheck the runtime user and ownership/permissions on mounted files and volumes.
Image build is unexpectedly slowCheck build context size, restore layer order, cache usage, and dependency downloads.

Minimal images may not contain shells, curl, ping, or package managers. Prefer application logs, Docker inspection, health endpoints, and dedicated diagnostic containers rather than installing debugging tools into every production image.

23. Container Security

  • Use trusted base images: choose official or verified publisher images and keep them updated.
  • Run as non-root: use an unprivileged user where supported and ensure required directories are writable.
  • Keep images minimal: avoid unnecessary packages, shells, compilers, and credentials.
  • Protect secrets: use secret stores or supported build/runtime secret mounts; never commit secrets or bake them into image layers.
  • Scan dependencies and images: include vulnerability scanning in development and CI workflows, and review findings.
  • Limit privileges: avoid privileged containers and unnecessary Linux capabilities.
  • Patch regularly: rebuild and redeploy when base images or dependencies receive security updates.
  • Pin and verify: use controlled versions and image digests where reproducibility is important.

For .NET, use a supported official runtime image. Review the image's documented default user and filesystem permissions for the specific tag you select. Do not assume every base image has identical defaults.

Security is layered. A container is not a security boundary that makes unsafe application code safe. Apply least privilege, secure configuration, network controls, patch management, and application-level authorization.

24. Image Size and Build Performance

Good image design improves transfer time, storage use, startup behavior, and attack-surface management. Useful practices include:

  • Use multi-stage builds to separate build tools from runtime content.
  • Copy project files and restore dependencies before copying frequently changing source files.
  • Use a targeted .dockerignore to keep the build context small.
  • Use BuildKit cache mounts where they make sense for package managers.
  • Choose a base image compatible with your native dependencies and operational requirements, not only the smallest image.
  • Remove unnecessary files and packages from the final stage.
  • Measure actual image size and build time; avoid assuming Alpine or distroless is always the best fit.
docker image ls
docker history catalog-api:1.0.0
docker build --pull -t catalog-api:1.0.1 .

--pull checks for a newer base image for the referenced tag. It is distinct from --no-cache, which disables build cache reuse. Pinning image digests improves reproducibility but means updates must be deliberately reviewed and applied.

25. Buildx and Multi-Platform Images

Developers may build on one architecture and deploy to another, such as building on an ARM-based laptop for an AMD64 Linux server. Docker Buildx extends the build experience with multi-platform builds and advanced BuildKit features.

docker buildx version
docker buildx ls

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t registry.example.com/team/catalog-api:1.0.0 \
  --push .

A multi-platform build can publish a manifest list that points to platform-specific image variants. The builder, base images, native dependencies, and build process must support the target architectures. Emulation can help, but native builds may be preferable for some workloads.

26. Registries, Tags and Image Publishing

A registry is where built images are stored for teams, CI/CD systems, and deployment platforms. Examples include Docker Hub, GitHub Container Registry, Azure Container Registry, and Amazon Elastic Container Registry.

docker login registry.example.com
docker tag catalog-api:1.0.0 \
  registry.example.com/team/catalog-api:1.0.0
docker push registry.example.com/team/catalog-api:1.0.0

Use meaningful version tags and a release policy. Avoid using latest as the only production identifier because it does not communicate which release is deployed. Restrict registry permissions and prefer short-lived or workload identities where available.

27. Testing Containerized Applications

Testing should cover both application behavior and the packaged runtime environment.

  • Unit tests: test business logic without requiring Docker for every test.
  • Integration tests: run against real dependencies such as a database when provider behavior matters.
  • Image smoke tests: start the built image and verify the process, health endpoint, and expected port.
  • Configuration tests: verify required settings are supplied and invalid settings fail clearly.
  • Security checks: scan the image and dependencies as part of CI.
  • Migration tests: verify schema migration and startup sequencing without assuming the database is instantly ready.
dotnet test
docker build -t catalog-api:test .
docker run -d --name catalog-api-test -p 8080:8080 catalog-api:test
# Verify the health endpoint with your preferred HTTP client.
docker logs catalog-api-test
docker rm -f catalog-api-test

Automated tests should use deterministic setup and teardown. Never point a test pipeline at a production database unless the test has been explicitly designed and approved for that environment.

28. CI/CD with Container Images

CommitBuild + testScan imagePush imageDeploy

A typical pipeline creates one tested image artifact and promotes it through environments.

A container-oriented pipeline commonly performs these steps:

  1. Restore dependencies and compile the application.
  2. Run unit and integration tests.
  3. Build the container image.
  4. Scan the image and dependencies.
  5. Tag and push the image to a registry.
  6. Deploy the same immutable image to the target environment.
  7. Verify health and monitor the rollout.

Tools may include GitHub Actions, Azure DevOps Pipelines, GitLab CI, or another CI system. The pipeline should not rebuild a different image for each environment; promote the tested artifact and supply environment-specific configuration at deployment time.

29. Deployment Concepts

Containers can run on a developer workstation, a virtual machine, an on-premises server, or a managed cloud container platform. The best choice depends on traffic, availability, operational skills, compliance, cost, and scaling requirements.

Hosting optionTypical fit
Single Docker hostDevelopment, demos, small controlled workloads; operator manages host and availability.
Cloud container app serviceManaged application hosting with less infrastructure administration.
Managed container orchestrationMultiple services, scaling, scheduling, rollout and service-management requirements.
On-premises container platformWorkloads with data locality, network, or infrastructure constraints.

Containerization packages the application. It does not decide the deployment architecture, database strategy, load balancing, backups, observability, or disaster recovery for you.

30. Docker vs Podman and Kubernetes

TechnologyPrimary roleHow it relates
DockerContainer development platform and ecosystemBuild, run, distribute and manage containers and images.
PodmanContainer engineCan run containers and pods; supports daemonless workflows and rootless operation.
KubernetesContainer orchestration platformSchedules and manages containerized workloads across a cluster.
Docker ComposeMulti-container application definitionUseful for local development and simpler multi-service environments; not a replacement for cluster orchestration.

Learn Docker fundamentals first. Then study Kubernetes when you need to understand cluster scheduling, service discovery, scaling, rollout management, and workload orchestration.

31. Best Practices and Common Mistakes

Best practices

  • Keep one primary application concern per container and separate independently operated services.
  • Use multi-stage builds and an appropriate runtime image.
  • Keep the build context small with .dockerignore.
  • Run as a non-root user where practical.
  • Store state outside ephemeral container filesystems.
  • Use health checks, graceful shutdown, logs, and resource limits appropriate to the platform.
  • Use explicit image versions and update them through a reviewed process.
  • Keep secrets outside images and source control.
  • Automate builds, tests, scans, and image publishing.

Common mistakes

MistakeBetter approach
Using localhost for a different Compose serviceUse the service name and container port.
Assuming EXPOSE publishes a portPublish with -p or Compose ports.
Putting SDK and source code in production imageUse a multi-stage build.
Storing database data only in container layerUse a volume or external database storage.
Hardcoding secrets in DockerfileInject secrets securely at build or runtime as appropriate.
Using latest as the only release referenceUse versioned tags and controlled digests.
Assuming a running container is readyUse health checks and application-level resilience.
Deleting volumes while cleaning upUnderstand the effect of down -v before running it.

32. Practical .NET 10 Mini Project: Containerized Catalog API

Build a small ASP.NET Core Web API and run it with SQL Server using Docker Compose. The goal is to understand the complete development workflow, not to create a production-ready e-commerce system.

Step 1: Create the project

dotnet new webapi -n CatalogApi
cd CatalogApi

Use the .NET 10 SDK and target net10.0. Keep the generated project structure and add an endpoint:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapGet("/", () => Results.Ok(new
{
    Application = "Catalog API",
    Framework = ".NET 10",
    Status = "Running"
}));

app.MapHealthChecks("/health");

app.Run();

This simple example uses modern C# syntax and is intentionally small. Add EF Core and SQL Server persistence as a separate exercise after the container workflow works.

Step 2: Add the Dockerfile

# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["CatalogApi.csproj", "./"]
RUN dotnet restore "CatalogApi.csproj"
COPY . .
RUN dotnet publish "CatalogApi.csproj" -c Release -o /app/publish --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "CatalogApi.dll"]

Step 3: Add .dockerignore

**/bin/
**/obj/
.git/
.vs/
.vscode/
.env
secrets/

Step 4: Build and run

docker build -t catalog-api:1.0.0 .
docker run --rm -d --name catalog-api -p 8080:8080 catalog-api:1.0.0

Test http://localhost:8080/ and http://localhost:8080/health. Review logs, stop the container, and start it again.

Step 5: Extend the project

  1. Add a SQL Server service using Compose and a named volume.
  2. Configure the API connection string using environment variables.
  3. Add EF Core migrations and a safe database initialization strategy.
  4. Add health checks that reflect the intended readiness behavior.
  5. Write unit and integration tests.
  6. Build, scan, tag, and publish the image in a CI pipeline.
  7. Deploy the same image to a test environment with external configuration.
Completion goal: You should be able to explain how source code becomes an image, how a container starts, how the API reaches SQL Server, where database data persists, and how the image moves through a deployment pipeline.

33. Interview Questions and Answers

1. What is containerization?

It is the practice of packaging an application and its required dependencies into an image that can run as an isolated container on a compatible container host.

2. What is the difference between an image and a container?

An image is a packaged, read-only template. A container is an instance created from that image with runtime configuration and a writable layer.

3. How are containers different from virtual machines?

VMs run guest operating systems on virtualized hardware. Containers use operating-system isolation and share the host kernel.

4. What is Docker Engine?

It is the service and API that manages container resources such as images, containers, networks, and volumes.

5. What is a Dockerfile?

A text file containing instructions that a builder uses to create a container image.

6. Why use multi-stage builds in .NET?

They allow the SDK to restore, compile, and publish the app in a build stage while the final stage contains only the published output and runtime requirements.

7. What is the difference between COPY and ADD?

COPY copies files from the build context or another stage. ADD has additional behavior such as handling supported remote sources and archive extraction. Prefer COPY unless ADD's extra behavior is intentionally needed.

8. What does EXPOSE do?

It documents the container port the application is intended to use. It does not publish that port to the host.

9. What is a Docker volume?

A Docker-managed storage location that exists independently of a container's writable layer and can be mounted into containers.

10. What is Docker Compose?

A tool for defining and running multi-container applications from a Compose YAML file.

11. Why should an API use db instead of localhost for SQL Server in Compose?

Because localhost inside the API container refers to that API container. The database service is reached through its Compose service name on the shared network.

12. How do you pass configuration to a container?

Use environment variables, mounted configuration, or a platform's configuration and secret-management facilities. Avoid baking environment-specific secrets into the image.

13. Why should containers be treated as ephemeral?

Containers can be replaced during deployments or recovery. Persistent application state should live in external services or managed storage rather than depending on a specific container instance.

14. How do you reduce image size?

Use multi-stage builds, appropriate base images, a small build context, dependency caching, and only the runtime files the application needs.

15. Is Docker the same as Kubernetes?

No. Docker is a container platform and ecosystem. Kubernetes orchestrates containerized workloads across a cluster.

16. How do you debug a container that exits?

Inspect its status and exit code, read logs, inspect configuration, and verify the entry point, environment, permissions, and dependencies.

17. Does running an app in a container make it secure?

No. Security still requires trusted images, least privilege, patching, secret protection, scanning, network controls, and secure application code.

18. What is an image digest?

A content-addressed identifier for an image manifest. It can be used to refer to a specific image artifact more reliably than a mutable tag.

19. What is Buildx used for?

Buildx provides an extended build interface, including BuildKit features and multi-platform image builds.

20. What is the difference between a running container and a healthy container?

Running indicates the container's main process is active. Healthy indicates a configured health check has passed.

34. Official Documentation and Further Learning

Use official documentation and release notes to verify commands, image tags, supported platforms, and security guidance before publishing or applying the examples to production. Docker and Microsoft container images evolve independently of this guide.

35. Final Thought

Docker is not simply a way to run an application inside a container. It changes how developers package, test, distribute, and operate software. Understanding images, containers, networks, volumes, configuration, and multi-stage builds helps you move from a project that runs only on your machine to an application that can be delivered consistently.

For .NET developers, start by containerizing a small ASP.NET Core API with a .NET 10 runtime image. Then add SQL Server through Compose, persist its data with a volume, and automate the image build and test process. The same container fundamentals apply to applications written in Java, Python, Node.js, Go, and other technologies.

Skills Tutorials – Learn • Build • Grow Together