Welcome Back to the Series: Building .NET Key Vault API
In the previous post, we set up a Dev Container for our project using a pre-built .NET
image. It was a quick and easy way to standardize development environments. Today, we’re stepping up by creating a custom Dockerfile. This approach not only gives us full control over the environment but also allows us to use the same image in our CI/CD pipeline for consistent builds.
By aligning the development environment with the pipeline, we eliminate "works on my machine" issues and ensure seamless integration across workflows.
Let’s get into it.
Why a Custom Dockerfile?
While a pre-built image like mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm
is convenient, it can be limiting when:
You need specific versions of tools like Terraform or Azure CLI.
Your project has unique dependencies not included in the base image.
You want to reuse the same environment for both development and pipelines.
With this approach, the Dockerfile serves as a single source of truth, ensuring consistency across local development and automated pipelines.
Great point! Incorporating the pipeline use case adds more context and utility to the newsletter. Here's the updated version, weaving in how the custom image serves as the base for a pipeline:
Custom Dockerfile for .NET Key Vault API
Let’s break down the Dockerfile step by step.
1. Base Image
We start by defining the base image for the container:
FROM mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm
Why this image? It’s purpose-built for
.NET 9
development, providing the runtime, SDK, and essential tools.Version Control - The
bookworm
tag ensures stability, using the latest Debian-based environment for.NET
.
2. Avoid Installation Prompts
To streamline package installations, we set the environment to noninteractive mode:
ENV NONINTERACTIVE=1
This avoids prompts during installation that could block the build process.
3. Installing Terraform: Step-by-Step Breakdown
Purpose:
Terraform is a tool for defining and provisioning infrastructure as code (IaC). In this project, it will help automate the creation and management of Azure resources like Key Vaults.
Here’s the relevant section of the Dockerfile:
ARG TERRAFORM_VERSION=1.10.3-1
RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list && \
apt-get update && \
apt-get install -y terraform=${TERRAFORM_VERSION} && \
apt-mark hold terraform && \
terraform -v
What’s Happening?
ARG TERRAFORM_VERSION=1.10.3-1
- Defines the Terraform version as a build argument.Adding HashiCorp’s GPG Key - HashiCorp signs its packages with a GPG key. This command downloads the key and stores it in a secure location.
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
Setting Up the Repository - Adds HashiCorp’s package repository to the system, scoped to the architecture (
amd64
) and distribution ($(lsb_release -cs)
outputs your Linux distro's codename, e.g.,bookworm
for Debian 12).
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list
Updating and Installing Terraform - Updates the package manager and installs the specific version of Terraform.
apt-get update && \
apt-get install -y terraform=${TERRAFORM_VERSION}
Version Locking - Prevents the installed version of Terraform from being updated inadvertently.
apt-mark hold terraform
Version Check - Verifies that the correct version of Terraform is installed.
terraform -v
If you would like to see the list of available versions you can do so here.
4. Installing Azure CLI: Step-by-Step Breakdown
Purpose:
The Azure CLI allows you to interact with Azure resources from the terminal. In this project, it’s essential for managing resources like Key Vaults and service principals.
Here’s the corresponding Dockerfile section:
ARG AZURE_CLI_VERSION=2.67.0
RUN wget -O- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft.asc.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft.asc.gpg] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list && \
apt-get update && \
apt-get install -y azure-cli=${AZURE_CLI_VERSION}-1~$(lsb_release -cs) && \
apt-mark hold azure-cli && \
az -v
What’s Happening?
ARG AZURE_CLI_VERSION=2.67.0
- Defines the Azure CLI version as a build argument, providing flexibility to set different versions if needed.Adding Microsoft’s GPG Key - Downloads and stores Microsoft’s GPG key, used to verify the authenticity of Azure CLI packages.
wget -O- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft.asc.gpg
Setting Up the Repository - Add Microsoft’s package repository to the system, scoped to the architecture (
amd64
) and the current Linux distribution ($(lsb_release -cs)
).
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft.asc.gpg] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list
Updating and Installing Azure CLI - Update the package list and installs the specified Azure CLI version.
apt-get update && \
apt-get install -y azure-cli=${AZURE_CLI_VERSION}-1~$(lsb_release -cs)
Version Locking - Lock the installed Azure CLI version to prevent unwanted updates.
apt-mark hold azure-cli
Version Check - Verify the installation by displaying the Azure CLI version.
az -v
If you would like to see the list of available versions you can do so here.
5. Clean Up
Finally, we clean up unnecessary files to reduce the image size:
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
This step ensures the resulting image is as lightweight as possible.
Key Features of the Dockerfile
Base Image:
Image -
mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm
Purpose-built for
.NET 9
development, this image provides a stable foundation.
Terraform Installation:
Version -
1.10.3
Ensures compatibility for Infrastructure-as-Code (IaC) tasks.
Azure CLI Installation:
Version -
2.67.0
Allows us to manage Azure resources directly from the container.
Reusable for Pipelines:
This Dockerfile isn’t just for local development. The same image can be built and pushed to a container registry (e.g., Azure Container Registry, Docker Hub) to power your pipeline.
Dev Container Configuration
Here’s how we connect the Dockerfile to our VS Code environment using devcontainer.json
:
{
"name": ".NET Key Vault Sample",
"dockerFile": "Dockerfile",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csdevkit",
"ms-dotnettools.vscodeintellicode-csharp",
"hashicorp.terraform"
]
}
}
}
What are we doing:
Custom Dockerfile Integration - The
dockerFile
field ensures VS Code uses our custom image.Extensions - Adding tools like
.NET Dev Kit
,Intellicode
, and Terraform support for an optimized development experience.
What We’ve Achieved
With this setup, we now have:
A Consistent Environment - Same tools and configurations for development and CI/CD.
Version Stability - Locked-down versions of .NET, Terraform and Azure CLI prevent unexpected updates and breaking changes, ensuring reliable workflows.
Custom Control - Flexibility to add or modify tools as the project evolves.
What’s Next?
In our next post, we’ll explore how to get up and running with key vaults within the portal before then showing you how to do that in terraform.
Ready to try this out? Let me know your thoughts, and don’t forget to subscribe for the next installment!
If you liked this post, don’t forget to check out my YouTube channel and video here…
Share this post