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.
Build an image once, then create containers from that image in different environments.
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
Simplified conceptual comparison. Containers share the host kernel; VMs run separate guest operating systems.
| Aspect | Virtual machine | Container |
|---|---|---|
| Operating system | Includes a guest OS | Shares the host kernel |
| Startup | Usually slower because a VM boots an OS | Usually starts faster as a process |
| Footprint | Generally larger | Generally smaller, depending on image |
| Isolation boundary | Virtualized hardware and guest OS | OS-level isolation; configuration matters |
| Typical use | Different operating systems, stronger VM boundary, legacy workloads | Portable 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.
05. Docker Architecture
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.
| Term | Meaning |
|---|---|
| Tag | A human-readable reference such as catalog-api:1.0.0. |
| Digest | A content-addressed identifier for an image manifest; useful for immutable references. |
| Layer | A reusable filesystem change contributing to an image. |
| Registry | A 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.
- Install or update the supported Docker Desktop release, or install Docker Engine on a supported Linux host.
- On Windows, enable the required virtualization features and configure WSL 2 when using that backend.
- Start Docker and wait until the engine is ready.
- 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.
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
| Command | Purpose |
|---|---|
docker images | List local images. |
docker ps | List running containers. |
docker ps -a | List all containers, including stopped ones. |
docker pull image:tag | Download an image. |
docker build -t name:tag . | Build an image from the current build context. |
docker run | Create and start a container. |
docker logs container | Read container standard output and error. |
docker exec -it container sh | Open a shell if the image includes one. |
docker inspect container | Inspect configuration and runtime details. |
docker stats | View live resource usage. |
docker rm container | Remove a stopped container. |
docker rmi image | Remove an image not in use. |
docker compose up -d | Create and start services defined in Compose. |
docker compose down | Stop 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/
| Instruction | What it does |
|---|---|
FROM | Selects a base image and starts a build stage. |
WORKDIR | Sets the working directory for following instructions. |
COPY | Copies files from the build context into the image. |
RUN | Executes a command during image build. |
ENV | Sets an environment variable in the image configuration. |
EXPOSE | Documents the port the application is intended to listen on; it does not publish the port. |
USER | Selects the user for subsequent build/runtime instructions. |
ENTRYPOINT | Defines the main executable for the container. |
CMD | Provides 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.
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.
.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.
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
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.
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
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.
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
| Symptom | What to investigate |
|---|---|
| Container exits immediately | Read logs, inspect exit code, check ENTRYPOINT and application startup errors. |
| Browser cannot connect | Check application listen address, container port, host port mapping, and firewall. |
| API cannot reach database | Check Compose network, service name, container port, credentials, and database readiness. |
| File changes are missing | Check build context, COPY instructions, bind mounts, and whether the image was rebuilt. |
| Permission denied | Check the runtime user and ownership/permissions on mounted files and volumes. |
| Image build is unexpectedly slow | Check 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.
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
.dockerignoreto 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
A typical pipeline creates one tested image artifact and promotes it through environments.
A container-oriented pipeline commonly performs these steps:
- Restore dependencies and compile the application.
- Run unit and integration tests.
- Build the container image.
- Scan the image and dependencies.
- Tag and push the image to a registry.
- Deploy the same immutable image to the target environment.
- 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 option | Typical fit |
|---|---|
| Single Docker host | Development, demos, small controlled workloads; operator manages host and availability. |
| Cloud container app service | Managed application hosting with less infrastructure administration. |
| Managed container orchestration | Multiple services, scaling, scheduling, rollout and service-management requirements. |
| On-premises container platform | Workloads 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
| Technology | Primary role | How it relates |
|---|---|---|
| Docker | Container development platform and ecosystem | Build, run, distribute and manage containers and images. |
| Podman | Container engine | Can run containers and pods; supports daemonless workflows and rootless operation. |
| Kubernetes | Container orchestration platform | Schedules and manages containerized workloads across a cluster. |
| Docker Compose | Multi-container application definition | Useful 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
| Mistake | Better approach |
|---|---|
| Using localhost for a different Compose service | Use the service name and container port. |
| Assuming EXPOSE publishes a port | Publish with -p or Compose ports. |
| Putting SDK and source code in production image | Use a multi-stage build. |
| Storing database data only in container layer | Use a volume or external database storage. |
| Hardcoding secrets in Dockerfile | Inject secrets securely at build or runtime as appropriate. |
| Using latest as the only release reference | Use versioned tags and controlled digests. |
| Assuming a running container is ready | Use health checks and application-level resilience. |
| Deleting volumes while cleaning up | Understand 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
- Add a SQL Server service using Compose and a named volume.
- Configure the API connection string using environment variables.
- Add EF Core migrations and a safe database initialization strategy.
- Add health checks that reflect the intended readiness behavior.
- Write unit and integration tests.
- Build, scan, tag, and publish the image in a CI pipeline.
- Deploy the same image to a test environment with external configuration.
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
- Docker Get Started — introductory Docker concepts and hands-on learning.
- Docker Engine documentation — engine architecture and operation.
- Docker Build documentation — Dockerfiles, BuildKit, caching and build best practices.
- Docker Compose documentation — multi-container applications and Compose Specification.
- Docker .NET language guide — containerize and develop .NET applications.
- Microsoft: Introduction to .NET and Docker — official .NET container guidance.
- Microsoft: Run ASP.NET Core in Docker — ASP.NET Core container guidance.
- What's new in C# 14 — language features for the .NET 10 era.
- What's new in .NET 10 — platform changes and release information.
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