<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Ogbonna</title>
    <description>The latest articles on DEV Community by John Ogbonna (@johnogbonna).</description>
    <link>https://dev.to/johnogbonna</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2525002%2F87236a2f-16f9-4f8a-8ae2-576b82e10484.jpeg</url>
      <title>DEV Community: John Ogbonna</title>
      <link>https://dev.to/johnogbonna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnogbonna"/>
    <language>en</language>
    <item>
      <title>DevOps by Doing: Deploying NGINX on Azure Kubernetes Service (AKS) with Terraform</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Sun, 05 Oct 2025 18:19:24 +0000</pubDate>
      <link>https://dev.to/johnogbonna/devops-by-doing-deploying-nginx-on-azure-kubernetes-service-aks-with-terraform-2an0</link>
      <guid>https://dev.to/johnogbonna/devops-by-doing-deploying-nginx-on-azure-kubernetes-service-aks-with-terraform-2an0</guid>
      <description>&lt;p&gt;Kubernetes has become the go-to platform for deploying and managing containerized applications at scale. In this guide, we’ll walk through how to provision an AKS cluster and deploy a sample NGINX application — all using Terraform.&lt;/p&gt;

&lt;p&gt;By the end, you’ll have a fully running NGINX service exposed on a public IP via an Azure Load Balancer, deployed automatically from Infrastructure as Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Terraform with AKS?
&lt;/h2&gt;

&lt;p&gt;Terraform lets you manage both your infrastructure and your application workloads as code — a concept known as Infrastructure as Code (IaC).&lt;/p&gt;

&lt;p&gt;With IaC, you describe everything your environment needs (from clusters to pods) in declarative configuration files instead of setting them up manually. This makes your deployments consistent, repeatable, and version-controlled — just like your application source code.&lt;/p&gt;

&lt;p&gt;In practice, Terraform can handle both:&lt;/p&gt;

&lt;p&gt;Provisioning the AKS cluster (the infrastructure)&lt;/p&gt;

&lt;p&gt;Deploying the Kubernetes resources (the workloads)&lt;/p&gt;

&lt;p&gt;That means no need to manually run az aks create or kubectl apply. Terraform can build the cluster, connect to it, and deploy your app — all in one automated workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you start, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An Azure account&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terraform installed (v1.5+)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;kubectl installed and configured&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Azure CLI installed and authenticated&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Terraform Configuration
&lt;/h3&gt;

&lt;p&gt;Create a new directory for your project and a file named &lt;code&gt;main.tf&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;terraform-aks-nginx
&lt;span class="nb"&gt;cd &lt;/span&gt;terraform-aks-nginx
&lt;span class="nb"&gt;touch &lt;/span&gt;main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following Terraform code to &lt;code&gt;main.tf&lt;/code&gt; to define your AKS cluster and supporting resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform &lt;span class="o"&gt;{&lt;/span&gt;
  required_providers &lt;span class="o"&gt;{&lt;/span&gt;
    azurerm &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;source&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt;3.0"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

provider &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  features &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

resource &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"aks_rg"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  name     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aks-nginx-rg"&lt;/span&gt;
  location &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"East US"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

resource &lt;span class="s2"&gt;"azurerm_kubernetes_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"aks_cluster"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  name                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nginx-aks-cluster"&lt;/span&gt;
  location            &lt;span class="o"&gt;=&lt;/span&gt; azurerm_resource_group.aks_rg.location
  resource_group_name &lt;span class="o"&gt;=&lt;/span&gt; azurerm_resource_group.aks_rg.name
  dns_prefix          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nginxaksdemo"&lt;/span&gt;

  default_node_pool &lt;span class="o"&gt;{&lt;/span&gt;
    name       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
    node_count &lt;span class="o"&gt;=&lt;/span&gt; 1
    vm_size    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_B2s"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  identity &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SystemAssigned"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

output &lt;span class="s2"&gt;"kube_config"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  value     &lt;span class="o"&gt;=&lt;/span&gt; azurerm_kubernetes_cluster.aks_cluster.kube_config_raw
  sensitive &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7vi9gpligbz20acgstj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7vi9gpligbz20acgstj.png" alt="main setup" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;Explanation for each component in &lt;code&gt;main.tf&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component / Block&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description / Purpose&lt;/th&gt;
&lt;th&gt;Tags / Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Block&lt;/td&gt;
&lt;td&gt;Defines global Terraform settings and required providers.&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;required_providers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sub-block&lt;/td&gt;
&lt;td&gt;Specifies providers Terraform uses (here, AzureRM).&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;azurerm&lt;/code&gt; is from HashiCorp registry, version &lt;code&gt;~&amp;gt;3.0&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provider "azurerm"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provider&lt;/td&gt;
&lt;td&gt;Connects Terraform to Microsoft Azure resources.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;features {}&lt;/code&gt; enables provider functionality (mandatory).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;azurerm_resource_group&lt;/code&gt; &lt;code&gt;"aks_rg"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Resource&lt;/td&gt;
&lt;td&gt;Creates an Azure Resource Group to organize related resources.&lt;/td&gt;
&lt;td&gt;Tags added: &lt;code&gt;environment&lt;/code&gt;, &lt;code&gt;project&lt;/code&gt;, &lt;code&gt;managed_by&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Name of the resource group (&lt;code&gt;aks-nginx-rg&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Descriptive and project-specific.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Azure region where resources are deployed (&lt;code&gt;West US&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Matches cluster location.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;azurerm_kubernetes_cluster&lt;/code&gt; &lt;code&gt;"aks_cluster"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Resource&lt;/td&gt;
&lt;td&gt;Deploys an Azure Kubernetes Service (AKS) cluster.&lt;/td&gt;
&lt;td&gt;Tags match the resource group for consistency.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Name of the AKS cluster (&lt;code&gt;nginx-aks-cluster&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Helps identify cluster.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Inherits location from resource group.&lt;/td&gt;
&lt;td&gt;Ensures both resources are co-located.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resource_group_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Links AKS cluster to the resource group created above.&lt;/td&gt;
&lt;td&gt;References &lt;code&gt;azurerm_resource_group.aks_rg.name&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dns_prefix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Prefix for the cluster’s public DNS name.&lt;/td&gt;
&lt;td&gt;Used by Azure-managed API server endpoint.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default_node_pool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Block&lt;/td&gt;
&lt;td&gt;Defines node configuration (VM size, count, etc.).&lt;/td&gt;
&lt;td&gt;Node pool name: &lt;code&gt;default&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;node_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Number of worker nodes to start with (here, &lt;code&gt;1&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Can be scaled later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vm_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;VM type used for each node (&lt;code&gt;Standard_B2s&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Small, cost-effective VM for demos.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;identity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Block&lt;/td&gt;
&lt;td&gt;Assigns a managed identity for Azure authentication.&lt;/td&gt;
&lt;td&gt;Type &lt;code&gt;SystemAssigned&lt;/code&gt; allows Azure to manage the identity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute Block&lt;/td&gt;
&lt;td&gt;Adds metadata to resources for organization and management.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;environment&lt;/code&gt;, &lt;code&gt;project&lt;/code&gt;, &lt;code&gt;managed_by&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output "kube_config"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Output Block&lt;/td&gt;
&lt;td&gt;Displays AKS kubeconfig data for &lt;code&gt;kubectl&lt;/code&gt; access.&lt;/td&gt;
&lt;td&gt;Marked &lt;code&gt;sensitive = true&lt;/code&gt; to hide from logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensitive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute&lt;/td&gt;
&lt;td&gt;Protects sensitive data from appearing in plain text output.&lt;/td&gt;
&lt;td&gt;Ensures secrets are hidden.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags.environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tag&lt;/td&gt;
&lt;td&gt;Describes the environment (e.g., &lt;code&gt;demo&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;, &lt;code&gt;dev&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Helps separate environments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags.project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tag&lt;/td&gt;
&lt;td&gt;Indicates which project the resource belongs to (&lt;code&gt;nginx-aks&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Aids in billing and tracking.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags.managed_by&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tag&lt;/td&gt;
&lt;td&gt;Specifies management source (&lt;code&gt;terraform&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Helps identify IaC-managed resources.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 2: Initialize and Apply Terraform
&lt;/h3&gt;

&lt;p&gt;Run the following commands to create your AKS cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This process will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a resource group&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy an AKS cluster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Output the Kubernetes configuration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Connect to Your AKS Cluster
&lt;/h3&gt;

&lt;p&gt;After deployment, connect &lt;code&gt;kubectl&lt;/code&gt; to your new cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks get-credentials &lt;span class="nt"&gt;--resource-group&lt;/span&gt; aks-nginx-rg &lt;span class="nt"&gt;--name&lt;/span&gt; nginx-aks-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Deploy NGINX
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Create a file named &lt;code&gt;nginx.yaml&lt;/code&gt; with the following content:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf6m9elzcp56y6qzsarq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf6m9elzcp56y6qzsarq.png" alt="nginxyaml" width="800" height="706"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deploy two NGINX pods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expose them through a LoadBalancer Service, which automatically provisions an Azure Load Balancer&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Verify Deployment
&lt;/h3&gt;

&lt;p&gt;Check the running pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And get the external IP address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get service nginx-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6kigvbki8443ktmp4wz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6kigvbki8443ktmp4wz.png" alt="kubectl get service" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You should see something like:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjac0yeclf2sh8mf9665.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjac0yeclf2sh8mf9665.png" alt="pods" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do You Need a Load Balancer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The LoadBalancer type service is not strictly required but highly recommended if you want public access.&lt;br&gt;
If you only need internal or private traffic, you can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ClusterIP – for internal cluster communication only&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NodePort – for debugging or development access on node IPs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 6: Clean Up
&lt;/h3&gt;

&lt;p&gt;To remove all resources and avoid extra costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once complete, the Resource Group will be destroyed; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjybeev7oy3a686aeoqvy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjybeev7oy3a686aeoqvy.png" alt="Resource Group will be destroyed" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ve successfully deployed a scalable NGINX application on Azure Kubernetes Service (AKS) using Terraform!&lt;br&gt;
This setup demonstrates how infrastructure as code (IaC) simplifies provisioning and how AKS handles orchestration and scaling out of the box.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>DevOps by Doing: Setting Up a Complete Modern DevOps Environment — Part 2</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Sun, 21 Sep 2025 18:05:13 +0000</pubDate>
      <link>https://dev.to/johnogbonna/devops-by-doing-setting-up-a-complete-modern-devops-environment-part-2-3pf1</link>
      <guid>https://dev.to/johnogbonna/devops-by-doing-setting-up-a-complete-modern-devops-environment-part-2-3pf1</guid>
      <description>&lt;h2&gt;
  
  
  What this part covers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;In Part 1, we built the foundation: Git setup, Node.js app, dependencies, and automated tests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Part 2, we’ll take the next big steps toward a production-ready setup by:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creating a &lt;code&gt;.gitignore&lt;/code&gt; file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up a &lt;code&gt;.env.example&lt;/code&gt; for environment variables&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding &lt;code&gt;ESLint&lt;/code&gt; for code quality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing a &lt;code&gt;Dockerfile&lt;/code&gt; to containerize our app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating a &lt;code&gt;.dockerignore&lt;/code&gt; to slim images&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using Docker Compose for local development&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running &amp;amp; testing the app in Docker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up GitHub repository + pushing code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configuring GitHub Actions CI/CD pipeline for build + test&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploying to GitHub (first automated deployment)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitoring build/deployment success&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;By the end of this article, you’ll have your project running in Docker, connected to GitHub, and building automatically via CI/CD.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: GitHub Actions CI/CD Pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this step does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re now moving from local development into automation. In this step, we’ll create a GitHub Actions pipeline that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Runs linting, tests, and security audits on every push and pull request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Builds and pushes Docker images to GitHub’s container registry (GHCR).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runs a vulnerability scan against the built image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploys automatically to staging (develop branch) and production (main branch).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the backbone of modern DevOps: CI/CD automation that ensures code is tested, packaged, secured, and ready for deployment — with no manual intervention required.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create workflow directory:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the GitHub Actions directory structure&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; github/workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create ci.yml:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; .github/workflows/ci.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create the pipeline definition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;name: CI/CD Pipeline

on:
  push:
    branches: &lt;span class="o"&gt;[&lt;/span&gt; main, develop &lt;span class="o"&gt;]&lt;/span&gt;
    tags: &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'v*'&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
  pull_request:
    branches: &lt;span class="o"&gt;[&lt;/span&gt; main &lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="nb"&gt;env&lt;/span&gt;:
  REGISTRY: ghcr.io
  IMAGE_NAME: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ github.repository &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

concurrency:
  group: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ github.workflow &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ github.ref &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
  cancel-in-progress: &lt;span class="nb"&gt;true

jobs&lt;/span&gt;:
  &lt;span class="nb"&gt;test&lt;/span&gt;:
    name: Test &amp;amp; Lint
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: &lt;span class="o"&gt;[&lt;/span&gt;20, 22]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ matrix.node-version &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
        uses: actions/setup-node@v4
        with:
          node-version: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ matrix.node-version &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          cache: &lt;span class="s1"&gt;'npm'&lt;/span&gt;

      - name: Install dependencies
        run: npm ci

      - name: Run linting
        run: npm run lint

      - name: Run tests
        run: npm &lt;span class="nb"&gt;test&lt;/span&gt;

      - name: Security audit
        run: npm audit &lt;span class="nt"&gt;--audit-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;critical &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Audit completed with warnings"&lt;/span&gt;

  build:
    name: Build &amp;amp; Push Image
    runs-on: ubuntu-latest
    needs: &lt;span class="nb"&gt;test
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;: github.event_name &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'push'&lt;/span&gt;

    permissions:
      contents: &lt;span class="nb"&gt;read
      &lt;/span&gt;packages: write
      security-events: write

    outputs:
      image-tag: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ steps.meta.outputs.tags &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
      image-digest: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ steps.build.outputs.digest &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          platforms: linux/amd64,linux/arm64

      - name: Log &lt;span class="k"&gt;in &lt;/span&gt;to Container Registry
        uses: docker/login-action@v3
        with:
          registry: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ env.REGISTRY &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          username: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ github.actor &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          password: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.GITHUB_TOKEN &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

      - name: Extract metadata
        &lt;span class="nb"&gt;id&lt;/span&gt;: meta
        uses: docker/metadata-action@v5
        with:
          images: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ env.REGISTRY &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ env.IMAGE_NAME &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          tags: |
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ref,event&lt;span class="o"&gt;=&lt;/span&gt;branch
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ref,event&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;pr
            type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;semver,pattern&lt;span class="o"&gt;={{&lt;/span&gt;version&lt;span class="o"&gt;}}&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;semver,pattern&lt;span class="o"&gt;={{&lt;/span&gt;major&lt;span class="o"&gt;}}&lt;/span&gt;.&lt;span class="o"&gt;{{&lt;/span&gt;minor&lt;span class="o"&gt;}}&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sha,prefix&lt;span class="o"&gt;={{&lt;/span&gt;branch&lt;span class="o"&gt;}}&lt;/span&gt;-
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;raw,value&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ github.run_id &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;raw,value&lt;span class="o"&gt;=&lt;/span&gt;latest,enable&lt;span class="o"&gt;={{&lt;/span&gt;is_default_branch&lt;span class="o"&gt;}}&lt;/span&gt;
          labels: |
            org.opencontainers.image.title&lt;span class="o"&gt;=&lt;/span&gt;DevOps Lab 2025
            org.opencontainers.image.description&lt;span class="o"&gt;=&lt;/span&gt;Modern Node.js DevOps application

      - name: Build and push Docker image
        &lt;span class="nb"&gt;id&lt;/span&gt;: build
        uses: docker/build-push-action@v5
        with:
          context: &lt;span class="nb"&gt;.&lt;/span&gt;
          platforms: linux/amd64,linux/arm64
          push: &lt;span class="nb"&gt;true
          &lt;/span&gt;tags: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ steps.meta.outputs.tags &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          labels: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ steps.meta.outputs.labels &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          cache-from: &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gha
          cache-to: &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gha,mode&lt;span class="o"&gt;=&lt;/span&gt;max
          target: production

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@0.24.0
        with:
          image-ref: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ steps.meta.outputs.tags &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          format: &lt;span class="s1"&gt;'sarif'&lt;/span&gt;
          output: &lt;span class="s1"&gt;'trivy-results.sarif'&lt;/span&gt;
          severity: &lt;span class="s1"&gt;'CRITICAL,HIGH'&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="nt"&gt;-on-error&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;

      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v3
        &lt;span class="k"&gt;if&lt;/span&gt;: always&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; hashFiles&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'trivy-results.sarif'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
        with:
          sarif_file: &lt;span class="s1"&gt;'trivy-results.sarif'&lt;/span&gt;

  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: build
    &lt;span class="k"&gt;if&lt;/span&gt;: github.ref &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'refs/heads/develop'&lt;/span&gt;
    environment: staging

    steps:
      - name: Deploy to Staging
        run: |
          &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🚀 Deploying to staging environment..."&lt;/span&gt;
          &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Image: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ needs.build.outputs.image-tag &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
          &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Digest: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ needs.build.outputs.image-digest &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
          &lt;span class="c"&gt;# Add your staging deployment commands here (kubectl, helm, etc.)&lt;/span&gt;

  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: build
    &lt;span class="k"&gt;if&lt;/span&gt;: github.ref &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'refs/heads/main'&lt;/span&gt;
    environment: production

    steps:
      - name: Deploy to Production
        run: |
          &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🎯 Deploying to production environment..."&lt;/span&gt;
          &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Image: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ needs.build.outputs.image-tag &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
          &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Digest: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ needs.build.outputs.image-digest &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
          &lt;span class="c"&gt;# Add your production deployment commands here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures we only build/test on pushes to main or develop, pull requests to main, or tagged releases.&lt;/li&gt;
&lt;li&gt;Runs in Node 20 and 22 to validate across multiple versions&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For the building and pushing of Docker Images, it only runs on push events and after tests pass it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Buildx for multi-arch images (amd64 + arm64).&lt;/li&gt;
&lt;li&gt;Pushes to GitHub Container Registry.&lt;/li&gt;
&lt;li&gt;Adds labels and tags for traceability.&lt;/li&gt;
&lt;li&gt;Runs Trivy for vulnerability scanning.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Separates staging and production deployments by branch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;develop → staging&lt;/li&gt;
&lt;li&gt;main → production&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgzk7ob9m9230ay842zx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgzk7ob9m9230ay842zx.png" alt="ci steps" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By the end of this step, you now have:&lt;br&gt;
✅ Automated linting, tests, and audits on every change.&lt;br&gt;
✅ Docker images built and pushed to GHCR automatically.&lt;br&gt;
✅ Security scans integrated directly into CI/CD.&lt;br&gt;
✅ Branch-based deployments ready for staging and production.&lt;/p&gt;

&lt;p&gt;This is the minimum baseline for modern DevOps — continuous integration, containerized builds, automated deployments, and built-in security.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Dockerfile
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this step does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Dockerfile defines how to package your application into a container image that can run consistently anywhere — locally, in CI/CD pipelines, or in cloud environments.&lt;/p&gt;

&lt;p&gt;In this step, we’ll create a production-ready Dockerfile that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Uses multi-stage builds to keep images small.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runs as a non-root user for security.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sets up health checks for monitoring.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Includes signal handling for clean shutdowns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This makes your app portable, secure, and reliable.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Create Dockerfile
&lt;/h3&gt;

&lt;p&gt;Enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Dockerfile, Paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Multi-stage build for optimized image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dependencies&lt;/span&gt;

&lt;span class="c"&gt;# Update packages for security&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk upgrade &lt;span class="nt"&gt;--no-cache&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy package files first for better caching&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Install only production dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;


&lt;span class="c"&gt;# Production stage  &lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="c"&gt;# Update packages and install necessary tools&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk upgrade &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; curl dumb-init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/apk/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Create non-root user with proper permissions&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; 1001 &lt;span class="nt"&gt;-S&lt;/span&gt; nodejs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-S&lt;/span&gt; nodeuser &lt;span class="nt"&gt;-u&lt;/span&gt; 1001 &lt;span class="nt"&gt;-G&lt;/span&gt; nodejs

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy dependencies from previous stage with proper ownership&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=dependencies --chown=nodeuser:nodejs /app/node_modules ./node_modules&lt;/span&gt;

&lt;span class="c"&gt;# Copy application code with proper ownership&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=nodeuser:nodejs package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=nodeuser:nodejs app.js ./&lt;/span&gt;

&lt;span class="c"&gt;# Switch to non-root user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nodeuser&lt;/span&gt;

&lt;span class="c"&gt;# Expose port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="c"&gt;# Health check with proper timing for Node.js startup&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=10s --start-period=15s --retries=3 \&lt;/span&gt;
  CMD curl -f http://localhost:3000/health || exit 1

&lt;span class="c"&gt;# Use dumb-init for proper signal handling in containers&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dumb-init", "--"]&lt;/span&gt;

&lt;span class="c"&gt;# Start application&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1aedl1fup12zgi0efzgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1aedl1fup12zgi0efzgq.png" alt="Dockerfile Steps" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile Explained
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Dependencies Stage:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Based on node:20-alpine for a minimal footprint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installs only production dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cleans up cache to keep size small.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Production Stage
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Installs required tools (curl, dumb-init).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creates a non-root user (nodeuser) with proper permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copies dependencies and app code with correct ownership.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Defines a health check endpoint (/health).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uses dumb-init as the entrypoint for proper signal handling.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why this matters&lt;/p&gt;

&lt;p&gt;By containerizing your app with this Dockerfile, you gain:&lt;br&gt;
✅ Consistency: Same environment across dev, CI/CD, and production.&lt;br&gt;
✅ Security: Runs as non-root with patched Alpine base image.&lt;br&gt;
✅ Reliability: Health checks ensure containers are restarted if unhealthy.&lt;br&gt;
✅ Efficiency: Multi-stage builds keep images lean.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Essential Configuration Files
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this step does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Configuration files are the unsung heroes of a DevOps project. They define what to ignore, how tools behave, and what settings your project expects.&lt;/p&gt;

&lt;p&gt;In this step, we’ll set up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;.dockerignore&lt;/code&gt; – to keep Docker images lean.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; – to avoid committing unnecessary or sensitive files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;.env.example&lt;/code&gt; – a template for environment variables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;.eslintrc.js&lt;/code&gt; – to enforce coding standards.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Create &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This file ensures your Docker builds don’t copy unnecessary files into the image, making builds faster and images smaller
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
npm-debug.log*
.git
.github
.env
.env.local
.env.*.local
logs
*.log
coverage
.nyc_output
.vscode
.idea
*.swp
*.swo
.DS_Store
Thumbs.db
README.md
tests/
jest.config.js
.eslintrc*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dependencies
node_modules/
npm-debug.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage
coverage/
.nyc_output

# Environment variables
.env
.env.local
.env.*.local

# Logs
logs
*.log

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create &lt;code&gt;.env.example&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Server Configuration&lt;/span&gt;
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000
&lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="c"&gt;# Logging&lt;/span&gt;
&lt;span class="nv"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create ESLint configuration:
&lt;/h4&gt;

&lt;p&gt;create &lt;code&gt;.eslintrc.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;module.exports &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;env&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    node: &lt;span class="nb"&gt;true&lt;/span&gt;,
    es2021: &lt;span class="nb"&gt;true&lt;/span&gt;,
    jest: &lt;span class="nb"&gt;true&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  extends: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'eslint:recommended'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
  parserOptions: &lt;span class="o"&gt;{&lt;/span&gt;
    ecmaVersion: 12,
    sourceType: &lt;span class="s1"&gt;'module'&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  rules: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;'no-console'&lt;/span&gt;: &lt;span class="s1"&gt;'off'&lt;/span&gt;,
    &lt;span class="s1"&gt;'no-unused-vars'&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'argsIgnorePattern'&lt;/span&gt;: &lt;span class="s1"&gt;'^_'&lt;/span&gt; &lt;span class="o"&gt;}]&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkfp5t8sowpgh41udqd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkfp5t8sowpgh41udqd3.png" alt="config files" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Docker Compose for Development
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this step does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While a Dockerfile defines how to build your app’s container, Docker Compose makes it easy to run multiple services together (like your app + a database).&lt;/p&gt;

&lt;p&gt;With one command, you can spin up your entire development environment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;version: &lt;span class="s1"&gt;'3.8'&lt;/span&gt;

services:
  app:
    build: &lt;span class="nb"&gt;.&lt;/span&gt;
    ports:
      - &lt;span class="s2"&gt;"3000:3000"&lt;/span&gt;
    environment:
      - &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;development
      - &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000
    restart: unless-stopped
    healthcheck:
      &lt;span class="nb"&gt;test&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CMD"&lt;/span&gt;, &lt;span class="s2"&gt;"curl"&lt;/span&gt;, &lt;span class="s2"&gt;"-f"&lt;/span&gt;, &lt;span class="s2"&gt;"http://localhost:3000/health"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
      interval: 30s
      &lt;span class="nb"&gt;timeout&lt;/span&gt;: 10s
      retries: 3
      start_period: 10s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start your app with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(First stop the app if started with npm)&lt;/p&gt;

&lt;p&gt;Stop it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwtzl9pkzzb0bf8wm5zd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwtzl9pkzzb0bf8wm5zd.png" alt="docker" width="800" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Test Everything Locally
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this step does: Shows you how to actually run and test your application locally before deploying it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install and Test Locally&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install all dependencies from package.json&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Run your test suite to make sure everything works&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# Start the application server&lt;/span&gt;
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you’ll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests should pass with green checkmarks:&lt;/li&gt;
&lt;li&gt;✓ GET / should return welcome page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Server starts and shows:&lt;br&gt;
🚀 Server running at &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6hj224kwdu8z7h9poyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6hj224kwdu8z7h9poyl.png" alt="server running" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test endpoints (in a new terminal window):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:3000/         &lt;span class="c"&gt;# Homepage&lt;/span&gt;
curl http://localhost:3000/health   &lt;span class="c"&gt;# Health check JSON&lt;/span&gt;
curl http://localhost:3000/info     &lt;span class="c"&gt;# System info JSON  &lt;/span&gt;
curl http://localhost:3000/metrics  &lt;span class="c"&gt;# Prometheus metrics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results of test:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5k01y7u9oxjzy50mn3gs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5k01y7u9oxjzy50mn3gs.png" alt="endpoint test" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker Commands&lt;/p&gt;

&lt;p&gt;Use these to test your image and container directly (great for debugging fundamentals).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build image&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-devops-app:latest &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Run container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-devops-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  my-devops-app:latest

&lt;span class="c"&gt;# Check container status&lt;/span&gt;
docker ps
docker logs my-devops-container

&lt;span class="c"&gt;# Test health check&lt;/span&gt;
curl http://localhost:3000/health

&lt;span class="c"&gt;# Stop container&lt;/span&gt;
docker stop my-devops-container
docker &lt;span class="nb"&gt;rm &lt;/span&gt;my-devops-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker Compose Commands&lt;/p&gt;

&lt;p&gt;Use these for day-to-day development or when you have multiple services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start all services defined in docker-compose.yml&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# View real-time logs from all services&lt;/span&gt;
docker-compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Stop all services and clean up&lt;/span&gt;
docker-compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Deploy to GitHub
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this step does: Commits your code to Git and pushes it to GitHub so the automated CI/CD pipeline can start working.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Initial Commit&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add all files to Git staging area&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Create your first commit with a descriptive message&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit: Complete DevOps setup with working CI/CD"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to GitHub&lt;/p&gt;

&lt;p&gt;⚠️ Before running these commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to GitHub.com and create a new repository called my-devops-project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not initialize it with a README, .gitignore, or license (we already created those).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the repository URL from GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replace YOUR_GITHUB_USERNAME in the commands below with your actual username.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set main as the default branch&lt;/span&gt;
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main

&lt;span class="c"&gt;# Connect to your GitHub repository (UPDATE THIS URL!)&lt;/span&gt;
git remote add origin https://github.com/YOUR_GITHUB_USERNAME/my-devops-project.git

&lt;span class="c"&gt;# Push your code to GitHub for the first time&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What You’ll See&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your code appears in your new GitHub repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The CI/CD pipeline kicks in automatically (thanks to the GitHub Actions workflow you added earlier).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 6: Fix CI Security Scan Errors
&lt;/h2&gt;

&lt;p&gt;The Problem:&lt;br&gt;
When the pipeline first runs, the security scanner fails with this error:&lt;/p&gt;

&lt;p&gt;failed to parse the image name: could not parse reference: ghcr.io/USERNAME/my-devops-project:latest&lt;/p&gt;

&lt;p&gt;This happened because the workflow was hardcoding &lt;code&gt;:latest&lt;/code&gt; as the image tag. At the time of the scan, that tag didn’t exist yet in the GitHub Container Registry, so Trivy couldn’t find or parse the image reference.&lt;/p&gt;

&lt;p&gt;The Fix:&lt;br&gt;
Instead of scanning :latest, we need to scan the exact image tag that was built and pushed by the build job. GitHub Actions makes this available via the job output needs.build.outputs.image-tag.&lt;/p&gt;

&lt;p&gt;Edit .github/workflows/ci.yml and replace the security-scan section with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;security-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Security Scan&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push' &amp;amp;&amp;amp; github.ref == 'refs/heads/main'&lt;/span&gt;

  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Trivy vulnerability scanner&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aquasecurity/trivy-action@master&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ needs.build.outputs.image-tag }}&lt;/span&gt;
        &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sarif'&lt;/span&gt;
        &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Trivy scan results&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create the Staging Environment in GitHub&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to your repo Settings&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpuvx9uv5jgtk8oc8obe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpuvx9uv5jgtk8oc8obe.png" alt="repo Settings" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Environments, click new environment.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1k79ddb54hfjn77y9m7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1k79ddb54hfjn77y9m7a.png" alt="click new environment" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name it: &lt;code&gt;staging&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3488s5scy586bcby72k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3488s5scy586bcby72k.png" alt="staging" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In VS Save and Commit Your Code Changes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage changes&lt;/span&gt;
git add .github/workflows/ci.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Commit with a clear message&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Fixed security scan to use build job image tag"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Push to GitHub&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2n6r8egrcmvjifctkftk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2n6r8egrcmvjifctkftk.png" alt="push to github" width="800" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once pushed, GitHub Actions will re-run the pipeline and Trivy will correctly scan the newly built image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Kubernetes Deployment Configurations
&lt;/h2&gt;

&lt;p&gt;What this step does: Defines how your application should run in Kubernetes for both staging and production environments, including the number of replicas, resource limits, and health checks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create directories
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create directories for Kubernetes configurations&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; k8s/staging k8s/production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Staging Deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create &lt;code&gt;k8s/staging/deployment.yml&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;⚠️ IMPORTANT: Update &lt;code&gt;YOUR_GITHUB_USERNAME and YOUR_REPO_NAME&lt;/code&gt; in the image URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: devops-app-staging
  namespace: staging
spec:
  replicas: 2
  selector:
    matchLabels:
      app: devops-app
      environment: staging
  template:
    metadata:
      labels:
        app: devops-app
        environment: staging
    spec:
      containers:
      - name: app
        image: ghcr.io/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME:latest
        ports:
        - containerPort: 3000
        &lt;span class="nb"&gt;env&lt;/span&gt;:
        - name: NODE_ENV
          value: &lt;span class="s2"&gt;"staging"&lt;/span&gt;
        - name: PORT
          value: &lt;span class="s2"&gt;"3000"&lt;/span&gt;
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
&lt;span class="nt"&gt;---&lt;/span&gt;
apiVersion: v1
kind: Service
metadata:
  name: devops-app-service-staging
  namespace: staging
spec:
  selector:
    app: devops-app
    environment: staging
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  &lt;span class="nb"&gt;type&lt;/span&gt;: LoadBalancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Production Deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create &lt;code&gt;k8s/production/deployment.yml&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;⚠️ IMPORTANT: Update &lt;code&gt;YOUR_GITHUB_USERNAME&lt;/code&gt; and &lt;code&gt;YOUR_REPO_NAME&lt;/code&gt;  in the image URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: devops-app-production
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: devops-app
      environment: production
  template:
    metadata:
      labels:
        app: devops-app
        environment: production
    spec:
      containers:
      - name: app
        image: ghcr.io/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME:latest
        ports:
        - containerPort: 3000
        &lt;span class="nb"&gt;env&lt;/span&gt;:
        - name: NODE_ENV
          value: &lt;span class="s2"&gt;"production"&lt;/span&gt;
        - name: PORT
          value: &lt;span class="s2"&gt;"3000"&lt;/span&gt;
        resources:
          requests:
            memory: &lt;span class="s2"&gt;"128Mi"&lt;/span&gt;
            cpu: &lt;span class="s2"&gt;"100m"&lt;/span&gt;
          limits:
            memory: &lt;span class="s2"&gt;"256Mi"&lt;/span&gt;
            cpu: &lt;span class="s2"&gt;"200m"&lt;/span&gt;
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
&lt;span class="nt"&gt;---&lt;/span&gt;
apiVersion: v1
kind: Service
metadata:
  name: devops-app-service-production
  namespace: production
spec:
  selector:
    app: devops-app
    environment: production
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  &lt;span class="nb"&gt;type&lt;/span&gt;: LoadBalancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;k8s/production/deployment.yml&lt;/code&gt; as such&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4w3m3uid7mrthpo787p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4w3m3uid7mrthpo787p.png" alt="deployment.yml" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Key Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Namespaces: staging and production isolate environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replicas: Staging uses 2 replicas; production uses 3 for high availability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Probes: livenessProbe and readinessProbe ensure containers are healthy before traffic is routed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resources (Production only): CPU/memory requests and limits prevent resource exhaustion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Services: Both use LoadBalancer to expose the application externally.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 8: Complete Deployment Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this step does:&lt;/strong&gt; Demonstrates the full CI/CD process with branch-based deployments to staging and production, along with monitoring.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Branch-based Deployment Strategy
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Branch&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;develop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Automatically deploys to &lt;strong&gt;staging&lt;/strong&gt; environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Automatically deploys to &lt;strong&gt;production&lt;/strong&gt; environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pull requests&lt;/td&gt;
&lt;td&gt;Run tests only (no deployment)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  2. Deploy Changes
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Deploy to Staging
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create and switch to develop branch&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; develop

&lt;span class="c"&gt;# Make your changes, then commit and push&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add new feature"&lt;/span&gt;
git push origin develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GitHub Actions automatically builds the image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runs security scans and tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploys the app to staging (staging namespace in Kubernetes).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3.Deploy to Production
&lt;/h3&gt;

&lt;p&gt;When you’re ready to release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Switch to main branch&lt;/span&gt;
git checkout main

&lt;span class="c"&gt;# Merge changes from develop&lt;/span&gt;
git merge develop

&lt;span class="c"&gt;# Push to trigger production deployment&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjig1hv1u66ame7c6a9mn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjig1hv1u66ame7c6a9mn.png" alt="production deployment" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GitHub Actions runs the full pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploys the app to production (default namespace in Kubernetes).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔎 Monitor Deployments
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Check GitHub Actions status
&lt;/h4&gt;

&lt;p&gt;👉 &lt;code&gt;https://github.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME/actions&lt;/code&gt;, &lt;br&gt;
replace &lt;code&gt;YOUR_GITHUB_USERNAME&lt;/code&gt; and &lt;code&gt;YOUR_REPO_NAME&lt;/code&gt; accordingly&lt;/p&gt;

&lt;h4&gt;
  
  
  Check your container registry
&lt;/h4&gt;

&lt;p&gt;👉&lt;code&gt;https://github.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME/pkgs/container/my-devops-project&lt;/code&gt;&lt;br&gt;
replace &lt;code&gt;YOUR_GITHUB_USERNAME&lt;/code&gt; and &lt;code&gt;YOUR_REPO_NAME&lt;/code&gt; accordingly&lt;/p&gt;

&lt;h4&gt;
  
  
  Test your deployed applications
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Staging health check&lt;/span&gt;
curl https://your-staging-url.com/health

&lt;span class="c"&gt;# Production health check&lt;/span&gt;
curl https://your-production-url.com/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;In this second part of &lt;em&gt;DevOps by Doing&lt;/em&gt;, we took our project beyond the basics and built out a &lt;strong&gt;modern CI/CD pipeline&lt;/strong&gt; with GitHub Actions, Docker, and Kubernetes.  &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;We covered how to:  *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Automate testing and linting across multiple Node.js versions
&lt;/li&gt;
&lt;li&gt;✅ Build and publish Docker images to &lt;strong&gt;GitHub Container Registry (GHCR)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Deploy automatically to a &lt;strong&gt;staging environment&lt;/strong&gt; from the &lt;code&gt;develop&lt;/code&gt; branch
&lt;/li&gt;
&lt;li&gt;✅ Secure production deployments on the &lt;code&gt;main&lt;/code&gt; branch with environment protections
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these steps, you now have a &lt;strong&gt;complete DevOps workflow&lt;/strong&gt;: from writing code, to testing, to building, to deploying in a controlled and repeatable way.  &lt;/p&gt;

&lt;p&gt;This isn’t just theory—it’s the exact type of pipeline used in modern production teams. By now, you should feel confident that every push to your repo can be tested, built, and deployed automatically. That’s the heart of &lt;strong&gt;DevOps by Doing&lt;/strong&gt;.  &lt;/p&gt;

</description>
      <category>docker</category>
      <category>cicd</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>DevOps by Doing: Setting Up a Complete Modern DevOps Environment — Part 1</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Thu, 04 Sep 2025 06:33:49 +0000</pubDate>
      <link>https://dev.to/johnogbonna/devops-by-doing-setting-up-a-complete-modern-devops-environment-part-1-mj8</link>
      <guid>https://dev.to/johnogbonna/devops-by-doing-setting-up-a-complete-modern-devops-environment-part-1-mj8</guid>
      <description>&lt;p&gt;Welcome to DevOps by Doing, a hands-on series where we don’t just talk about DevOps principles — we actually build, maintain and upgrade complete modern DevOps environments step by step. This is the first instalment in the series: Setting Up a Complete Modern DevOps Environment. By the end of this exercise series, you’ll have a working Node.js application running through a full DevOps pipeline: from source control to automated testing, containerization, deployment and more.&lt;/p&gt;

&lt;p&gt;Instead of abstract theory, we’ll focus on practical implementation. We’ll start small — setting up our project and tools — and then gradually build out the pipeline into a production-ready workflow. Each article will layer on new pieces of the environment so you can follow along at your own pace.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What We’ll Cover in This Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Over the course of this episode (likely two to three parts), we’ll walk through the following:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Version Control Setup: Initializing Git, configuring .gitignore, and structuring the project repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Node.js Application Setup: Bootstrapping a simple sample app, installing dependencies, and writing tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code Quality &amp;amp; Standards: Setting up ESLint and other linters for clean, consistent code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Environment Management: Creating .env.example files to handle configuration safely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Continuous Integration (CI): Configuring GitHub Actions to run builds, linting, and tests automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Containerization: Writing a Dockerfile, setting up .dockerignore, and building images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Local Development with Docker Compose: Creating a docker-compose.yml to run our app and services locally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Continuous Deployment (CD): Extending our GitHub Actions pipeline to build, push, and deploy containers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Part 1: Laying the Foundation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  In this first article, we’ll focus on the core project setup:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Initializing Git&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up a sample Node.js app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installing dependencies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing and running tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating .gitignore and .env.example&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding ESLint for code quality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up files for GitHub Actions CI/CD&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up Dockerfile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Essential Configuration Files such as &lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this article, you’ll have a clean, version-controlled project with quality checks in place — ready to move on to Docker, GitHub Actions, and deployment in the upcoming parts.&lt;/p&gt;

&lt;p&gt;Let’s get started. 🚀&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;br&gt;
Before we dive in, let’s make sure your development environment is ready. To follow along with this series, you’ll need the following tools installed on your machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Git – for version control and repository management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Node.js(LTS recommended) – we’ll use it to build our sample application. Installing Node will also give you npm (Node Package Manager).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker to containerize our application and manage images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Compose to run multi-container environments locally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A GitHub account – since we’ll be pushing code to GitHub and setting up GitHub Actions for CI/CD.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A code editor (VS Code recommended) – with useful extensions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify Everything is Installed&lt;/p&gt;

&lt;p&gt;Once you’ve installed the required tools, run the following commands in your terminal to make sure everything is set up correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;    
&lt;span class="c"&gt;# Should show v18.x+ or v20.x+&lt;/span&gt;

npm &lt;span class="nt"&gt;--version&lt;/span&gt;     
&lt;span class="c"&gt;# Should show 9.x+ or 10.x+&lt;/span&gt;

git &lt;span class="nt"&gt;--version&lt;/span&gt;     
&lt;span class="c"&gt;# Should show 2.34+ &lt;/span&gt;

docker &lt;span class="nt"&gt;--version&lt;/span&gt;  
&lt;span class="c"&gt;# Should show 24.x+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe4gja0c3f81v0ojk8u1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe4gja0c3f81v0ojk8u1.png" alt="check" width="800" height="383"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Set Up Git for Version Control
&lt;/h2&gt;

&lt;p&gt;What this step does: Configures Git on your machine so it knows who you are when you make commits, and sets up proper project tracking.&lt;/p&gt;

&lt;p&gt;Git is the backbone of most modern DevOps workflows. It keeps track of changes to your codebase, allows collaboration, and integrates directly with CI/CD pipelines like GitHub Actions.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.1 Configure Git (if you haven’t already)
&lt;/h3&gt;

&lt;p&gt;Run the following commands to tell Git who you are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.name &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.email &lt;span class="s2"&gt;"your.email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can confirm your settings with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Use the same email that’s linked to your GitHub account — it makes authentication and contribution tracking easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Initialize a Git Repository
&lt;/h3&gt;

&lt;p&gt;Create and navigate to the folder where you’ll be creating the project and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-devops-project
&lt;span class="nb"&gt;cd &lt;/span&gt;my-devops-project
git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new folder called &lt;code&gt;my-devops-project&lt;/code&gt;, moves you inside it, and initializes it as a Git repository. The &lt;code&gt;git init&lt;/code&gt; command sets up all the hidden files Git needs (.git/), allowing you to start tracking changes to your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywtt9w5u6xhy3lh7hcd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywtt9w5u6xhy3lh7hcd.png" alt="git initialized" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To confirm that Git has created the repository metadata, list all files (including hidden ones):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output (your system may show additional files):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;   ..   .git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lu280d5wli326ffg13u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lu280d5wli326ffg13u.png" alt="show files" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 Verify Repository Setup
&lt;/h3&gt;

&lt;p&gt;Check that everything is working by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a message saying “On branch main” (or master, depending on your Git version) and that there are no commits yet.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx9grb8hnt3w417dsb69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx9grb8hnt3w417dsb69.png" alt=" no commits yet" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, Git is ready — next we’ll set up our Node.js application inside this repository. 🚀&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Build a Node.js Web App
&lt;/h2&gt;

&lt;p&gt;What this step does: Creates a web application using Node.js that can serve web pages and API endpoints.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.1 Initialize a Node.js Project
&lt;/h3&gt;

&lt;p&gt;What this step does: Creates a &lt;code&gt;package.json&lt;/code&gt; file that describes your project and manages dependencies.&lt;/p&gt;

&lt;p&gt;Run the following command inside your project folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a &lt;code&gt;package.json&lt;/code&gt; file with default values.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35669grmpmhaz5mov1dd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35669grmpmhaz5mov1dd.png" alt="package.json" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 Update &lt;code&gt;package.json&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;What this step does: Customizes the package.json with proper scripts and metadata for your DevOps project.&lt;/p&gt;

&lt;p&gt;Open the file in your editor (VS Code, for example), or recreate it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then copy and replace the contents with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-devops-project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DevOps learning project with Node.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node app.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node app.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint ."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"devops"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodejs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=18.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^29.7.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eslint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8.57.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"supertest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.1.4"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Create the Node.js Web Server
&lt;/h2&gt;

&lt;p&gt;What this step does: Creates an HTTP server that listens on port 3000, serves different endpoints (/, /health, /info, /metrics), includes security headers and error handling, provides graceful shutdown capability, and exports the server for testing.&lt;/p&gt;

&lt;p&gt;3.1 Create a file called&lt;code&gt;app.js&lt;/code&gt;. You can do this in VS code or by typing in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the following code into a new file called &lt;code&gt;app.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;requestCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Enhanced Web Server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;requestCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// CORS headers&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET, POST, PUT, DELETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Security headers&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Content-Type-Options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nosniff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Frame-Options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DENY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-XSS-Protection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1; mode=block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Route handling&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
        &amp;lt;!DOCTYPE html&amp;gt;
        &amp;lt;html&amp;gt;
        &amp;lt;head&amp;gt;
          &amp;lt;title&amp;gt;DevOps Lab 2025&amp;lt;/title&amp;gt;
          &amp;lt;style&amp;gt;
            body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
            .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; }
            .endpoint { background: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 5px; border-left: 4px solid #007bff; }
          &amp;lt;/style&amp;gt;
        &amp;lt;/head&amp;gt;
        &amp;lt;body&amp;gt;
          &amp;lt;div class="header"&amp;gt;
            &amp;lt;h1&amp;gt;I'm Getting Better at DevOps, Yay!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;Modern Node.js application with CI/CD pipeline&amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;h2&amp;gt;Available Endpoints:&amp;lt;/h2&amp;gt;
          &amp;lt;div class="endpoint"&amp;gt;
            &amp;lt;strong&amp;gt;GET /&amp;lt;/strong&amp;gt; - This welcome page
          &amp;lt;/div&amp;gt;
          &amp;lt;div class="endpoint"&amp;gt;
            &amp;lt;strong&amp;gt;GET /health&amp;lt;/strong&amp;gt; - Health check (JSON)
          &amp;lt;/div&amp;gt;
          &amp;lt;div class="endpoint"&amp;gt;
            &amp;lt;strong&amp;gt;GET /info&amp;lt;/strong&amp;gt; - System information
          &amp;lt;/div&amp;gt;
          &amp;lt;div class="endpoint"&amp;gt;
            &amp;lt;strong&amp;gt;GET /metrics&amp;lt;/strong&amp;gt; - Prometheus metrics
          &amp;lt;/div&amp;gt;
          &amp;lt;p&amp;gt;Environment: &amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;Server time: &amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;Requests served: &amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;requestCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;/body&amp;gt;
        &amp;lt;/html&amp;gt;
      `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;healthy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;uptime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptime&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;node_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;requests_served&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestCount&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;node_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;memory_usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryUsage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;uptime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;requestCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

# HELP app_uptime_seconds Application uptime in seconds
# TYPE app_uptime_seconds gauge
app_uptime_seconds &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptime&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;

# HELP nodejs_memory_usage_bytes Node.js memory usage
# TYPE nodejs_memory_usage_bytes gauge
nodejs_memory_usage_bytes{type="rss"} &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryUsage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
nodejs_memory_usage_bytes{type="heapUsed"} &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryUsage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;heapUsed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
nodejs_memory_usage_bytes{type="heapTotal"} &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryUsage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;heapTotal&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
nodejs_memory_usage_bytes{type="external"} &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryUsage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not Found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Route &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Graceful shutdown&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Received SIGTERM, shutting down gracefully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server closed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Received SIGINT, shutting down gracefully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server closed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start server&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🚀 Server running at http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Environment: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Node.js version: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Export server for testing&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fleecl0e2aiw08cccs1wq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fleecl0e2aiw08cccs1wq.png" alt="Node App" width="800" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What this code does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates an HTTP server that listens on port 3000&lt;/li&gt;
&lt;li&gt;Serves different endpoints (/, /health, /info, /metrics)&lt;/li&gt;
&lt;li&gt;Includes security headers and proper error handling&lt;/li&gt;
&lt;li&gt;Provides graceful shutdown capability&lt;/li&gt;
&lt;li&gt;Exports the server for testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 Install Dependencies
&lt;/h3&gt;

&lt;p&gt;What this step does: Downloads and installs the necessary packages for testing and code quality.&lt;/p&gt;

&lt;p&gt;Run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install testing and development tools&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; jest eslint supertest

&lt;span class="c"&gt;# Install all dependencies (creates node_modules folder)&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What you’ll see:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A new &lt;code&gt;node_modules/&lt;/code&gt; folder containing installed packages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;package-lock.json&lt;/code&gt; file that locks dependency versions for consistency across environments&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxiyyktc9cuapaewodlhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxiyyktc9cuapaewodlhk.png" alt="package-lock.json" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2&gt;
  
  
  Step 4: Create Proper Tests
&lt;/h2&gt;

&lt;p&gt;🎯 What this step does&lt;/p&gt;

&lt;p&gt;This step sets up automated testing for your application using Jest and Supertest. Automated tests help verify that your web app’s endpoints behave correctly and prevent regressions when making changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Create Tests Directory and File
&lt;/h3&gt;

&lt;p&gt;First, create a folder to hold your tests and an initial test file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#Create a folder for your tests&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;tests

&lt;span class="c"&gt;# Create the main test file&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;tests/app.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Your project folder should look like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-devops-project/
├── app.js
├── package.json
├── tests/
│   └── app.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58qt4esy4pn04wt3vh4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58qt4esy4pn04wt3vh4o.png" alt="folder structure" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Write Your First Tests
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;tests/app.test.js&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;const request &lt;span class="o"&gt;=&lt;/span&gt; require&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'supertest'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
const server &lt;span class="o"&gt;=&lt;/span&gt; require&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../app'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

describe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'App Endpoints'&lt;/span&gt;, &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  afterAll&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    server.close&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET / should return welcome page'&lt;/span&gt;, async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const response &lt;span class="o"&gt;=&lt;/span&gt; await request&lt;span class="o"&gt;(&lt;/span&gt;server&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.status&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.text&lt;span class="o"&gt;)&lt;/span&gt;.toContain&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DevOps Lab 2025'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET /health should return health status'&lt;/span&gt;, async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const response &lt;span class="o"&gt;=&lt;/span&gt; await request&lt;span class="o"&gt;(&lt;/span&gt;server&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/health'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.status&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.body.status&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'healthy'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.body.timestamp&lt;span class="o"&gt;)&lt;/span&gt;.toBeDefined&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;typeof response.body.uptime&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'number'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET /info should return system info'&lt;/span&gt;, async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const response &lt;span class="o"&gt;=&lt;/span&gt; await request&lt;span class="o"&gt;(&lt;/span&gt;server&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/info'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.status&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.body.platform&lt;span class="o"&gt;)&lt;/span&gt;.toBeDefined&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.body.node_version&lt;span class="o"&gt;)&lt;/span&gt;.toBeDefined&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET /metrics should return prometheus metrics'&lt;/span&gt;, async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const response &lt;span class="o"&gt;=&lt;/span&gt; await request&lt;span class="o"&gt;(&lt;/span&gt;server&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/metrics'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.status&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.text&lt;span class="o"&gt;)&lt;/span&gt;.toContain&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http_requests_total'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.text&lt;span class="o"&gt;)&lt;/span&gt;.toContain&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app_uptime_seconds'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET /nonexistent should return 404'&lt;/span&gt;, async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const response &lt;span class="o"&gt;=&lt;/span&gt; await request&lt;span class="o"&gt;(&lt;/span&gt;server&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/nonexistent'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.status&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;404&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    expect&lt;span class="o"&gt;(&lt;/span&gt;response.body.error&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Not Found'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✅ These tests check:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/ → Welcome page&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/health → Health check info&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/info → System details&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/metrics → Prometheus metrics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/nonexistent → Correct 404 handling&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.3 Configure Jest
&lt;/h3&gt;

&lt;p&gt;Next, set up a Jest configuration file so tests run consistently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch jest.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the following into jest.config.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
  testEnvironment: 'node',
  collectCoverage: true,
  coverageDirectory: 'coverage',
  testMatch: ['**/tests/**/*.test.js'],
  verbose: true
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Config breakdown:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;testEnvironment: 'node' → Run in Node.js context&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;collectCoverage: true → Generate coverage reports&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;coverageDirectory: 'coverage' → Store reports in /coverage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;testMatch → Look for files ending in .test.js inside /tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;verbose: true → Show detailed output&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.4 Run the Tests
&lt;/h3&gt;

&lt;p&gt;Now run the tests with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is working, you’ll see output like:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnteywiwrgrvh7s2iem48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnteywiwrgrvh7s2iem48.png" alt="output" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Start the App 🚀
&lt;/h2&gt;

&lt;p&gt;🎯 What this step does&lt;/p&gt;

&lt;p&gt;This step launches your Node.js web server so you can test it locally in your browser and confirm everything works before moving into Docker, CI/CD, and deployment in Part 2.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.1 Start the Server
&lt;/h3&gt;

&lt;p&gt;Run the following command in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F098kc9kp81ujkudyx6jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F098kc9kp81ujkudyx6jj.png" alt="start server" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Verify Endpoints in Browser or curl
&lt;/h3&gt;

&lt;p&gt;Now, open your browser (or use curl) to test the endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Welcome page&lt;/span&gt;
curl http://localhost:3000/

&lt;span class="c"&gt;# Health check&lt;/span&gt;
curl http://localhost:3000/health

&lt;span class="c"&gt;# Info&lt;/span&gt;
curl http://localhost:3000/info

&lt;span class="c"&gt;# Metrics&lt;/span&gt;
curl http://localhost:3000/metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Results: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnralvvsp2197b77s9rdx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnralvvsp2197b77s9rdx.png" alt=" Results" width="800" height="749"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will see this page on the browser:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2450iom8gytg1qpx9mks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2450iom8gytg1qpx9mks.png" alt="page on the browser" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ You should see the welcome HTML page at / and JSON responses at /health and /info. The /metrics endpoint will return Prometheus-style metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Stop the Server
&lt;/h3&gt;

&lt;p&gt;When you’re done testing, stop the server by pressing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CTRL + C&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 Wrapping Up Part 1
&lt;/h2&gt;

&lt;p&gt;In this first part of DevOps by Doing - Setting Up a Complete Modern DevOps Environment, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set up Git for version control&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Created a &lt;code&gt;Node.js&lt;/code&gt; project with a sample web app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installed dependencies and dev tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrote and ran automated tests with &lt;code&gt;Jest&lt;/code&gt; + &lt;code&gt;Supertest&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Started the app locally&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You now have the foundation of a modern DevOps-ready application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coming Up in Part 2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Part 2, we’ll take this project to the next level by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creating a &lt;code&gt;.gitignore&lt;/code&gt; file to keep your repo clean&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding a &lt;code&gt;.env.example&lt;/code&gt; for environment variables&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up &lt;code&gt;ESLint&lt;/code&gt; for code quality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing a &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running your app with Docker Compose&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preparing for CI/CD with GitHub Actions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stay tuned — things are about to get even more exciting! 🚀&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>cloud</category>
      <category>docker</category>
      <category>git</category>
    </item>
    <item>
      <title>Getting Started With Kubernetes: A Beginner’s Guide Using Minikube and KubeCTL</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Sun, 15 Jun 2025 19:10:22 +0000</pubDate>
      <link>https://dev.to/johnogbonna/getting-started-with-kubernetes-a-beginners-guide-using-minikube-and-kubectl-48hh</link>
      <guid>https://dev.to/johnogbonna/getting-started-with-kubernetes-a-beginners-guide-using-minikube-and-kubectl-48hh</guid>
      <description>&lt;p&gt;Kubernetes can feel intimidating at first, but with this  step-by-step guide, you’ll be up and running in no time. This article will walk you through the essentials of Kubernetes, introduce you to Minikube and KubeCTL, and help you launch your first local cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 What is Kubernetes?
&lt;/h2&gt;

&lt;p&gt;Kubernetes (sometimes called K8's) is an open-source system for managing containerized applications. It helps you automate deployment, scaling, networking, and health monitoring across clusters of machines. This process is also referred to as container orchestration. Kubernetes was originally developed by Google and is now maintained by the &lt;a href="https://www.cncf.io/" rel="noopener noreferrer"&gt;Cloud Native Computing Foundation (CNCF)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Minikube?
&lt;/h2&gt;

&lt;p&gt;Minikube is a tool that lets you run a local, single-node Kubernetes cluster on your laptop. It's ideal for developers who want to learn and test Kubernetes without using cloud services.&lt;/p&gt;

&lt;p&gt;Unlike managed platforms like EKS (AWS), AKS (Azure), or GKE (Google Cloud), which run in the cloud and handle production-scale clusters, Minikube provides a simplified, self-contained local environment for development and experimentation. It supports various virtualization drivers (Docker, VirtualBox, etc.) and includes all the components needed to simulate a real Kubernetes cluster locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Minikube:
&lt;/h3&gt;

&lt;p&gt;Navigate &lt;a href="https://minikube.sigs.k8s.io/docs/start/?arch=%2Fwindows%2Fx86-64%2Fstable%2F.exe+download" rel="noopener noreferrer"&gt;here&lt;/a&gt; to download Minikube. Select your OS and follow the instructions. Alternatively for quick installations: &lt;/p&gt;

&lt;p&gt;macOS (Homebrew)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;minikube
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows (Chocolatey)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;minikube&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
&lt;span class="nb"&gt;sudo install &lt;/span&gt;minikube-linux-amd64 /usr/local/bin/minikube
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What is Kubectl?
&lt;/h3&gt;

&lt;p&gt;kubectl is the command-line tool used to interact with a Kubernetes cluster. It lets you deploy apps, inspect resources, view logs, and manage your cluster—all by sending commands to the Kubernetes API.&lt;/p&gt;

&lt;p&gt;Whether you're using Minikube locally or a cloud service like EKS or AKS, kubectl is the primary way to control and monitor your cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing KubeCTL
&lt;/h2&gt;

&lt;p&gt;Navigate &lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;here&lt;/a&gt; to install KubeCTL&lt;br&gt;
Alternatively, for quick install: &lt;/p&gt;

&lt;p&gt;macOS (Homebrew)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows (Chocolatey)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubernetes-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LO&lt;/span&gt; &lt;span class="s2"&gt;"https://dl.k8s.io/release/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://dl.k8s.io/release/stable.txt&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/bin/linux/amd64/kubectl"&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x kubectl
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;kubectl /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify Installation:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl version &lt;span class="nt"&gt;--client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get this result: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fao5es5pc2fu962stq2ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fao5es5pc2fu962stq2ck.png" alt="You should get this result" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  🚀 Start Your First Local Cluster
&lt;/h3&gt;

&lt;p&gt;With Minikube and kubectl installed, you can now spin up your first Kubernetes cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may take a few minutes. Once it's done, check the node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a node listed with a Ready status as shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat08hit9zhvparh0d7e4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat08hit9zhvparh0d7e4.png" alt="single node" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Deploy a Sample App
&lt;/h3&gt;

&lt;p&gt;Let’s test the setup by deploying a basic Nginx web server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create deployment hello-nginx &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx
kubectl expose deployment hello-nginx &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NodePort &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Explanation of These Commands: &lt;br&gt;
-&lt;code&gt;kubectl create deployment hello-nginx --image=nginx&lt;/code&gt;: Creates a Deployment named hello-nginx using the official Nginx image. Kubernetes ensures the pod stays running.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;kubectl expose deployment hello-nginx --type=NodePort --port=80&lt;/code&gt;: Uses kubectl expose to make a deployment accessible externally. This creates a service that routes external traffic on a static port to the hello-nginx pods.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then open it in your browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube service hello-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu48dxnrkzjtd5h7uipi0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu48dxnrkzjtd5h7uipi0.png" alt="commands" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will open a browser window pointing to the Nginx welcome page served from your local Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoh7knwxkbytui6me2vo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoh7knwxkbytui6me2vo.png" alt="Browser" width="576" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Recap
&lt;/h3&gt;

&lt;p&gt;We just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Learned about Kubernetes, Minikube and KubeCTL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installed Minikube and KubeCTL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Launched a local Kubernetes cluster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deployed and accessed a containerized app&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're now ready to dive deeper into Kubernetes concepts like Pods, Services, ReplicaSets, Deployments, and more — all from your local machine with the help of Minikube.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Azure Applied Skills: Create DNS zones and configure DNS settings</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Sun, 27 Apr 2025 05:08:54 +0000</pubDate>
      <link>https://dev.to/johnogbonna/azure-applied-skills-create-dns-zones-and-configure-dns-settings-48m</link>
      <guid>https://dev.to/johnogbonna/azure-applied-skills-create-dns-zones-and-configure-dns-settings-48m</guid>
      <description>&lt;p&gt;This is the last exercise in the Azure Networking series. Here we will: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create and configure a private DNS zone.&lt;/li&gt;
&lt;li&gt;Create and configure DNS records.&lt;/li&gt;
&lt;li&gt;Configure DNS settings on a virtual network.
We will be building on what we did in &lt;a href="https://dev.to/johnogbonna/azure-applied-skills-configure-network-routing-22he"&gt;this exercise&lt;/a&gt; so make sure this exercise is done first&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exercise 05: Create DNS zones and configure DNS settings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a private DNS zone
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On the Azure portal, search for and select Private dns zones &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvfoytu3gv1si27omxcu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvfoytu3gv1si27omxcu.png" alt="select Private dns zones " width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select + Create and configure the DNS zone: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subscription: Select your subscription&lt;/li&gt;
&lt;li&gt;Resource group: Your RG&lt;/li&gt;
&lt;li&gt;Name: private.contoso.com&lt;/li&gt;
&lt;li&gt;Region: East US&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Select Review + create and then select Create.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Wait for the DNS zone to deploy, and then select Go to resource.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2ul7u3yiicjuh03yo8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2ul7u3yiicjuh03yo8q.png" alt="select Go to resource" width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a virtual network link to your private DNS zone
&lt;/h3&gt;

&lt;p&gt;In order to resolve DNS records in a private DNS zone, resources must be linked to the private zone. A virtual network link associates the virtual network to the private zone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Working with the private.contoso.com DNS zone, in the DNS Management blade, select Virtual network links.&lt;/li&gt;
&lt;li&gt;Select + Add” and enter:

&lt;ul&gt;
&lt;li&gt;Link name: app-vnet-link&lt;/li&gt;
&lt;li&gt;Virtual network: app-vnet&lt;/li&gt;
&lt;li&gt;Enable auto registration: Enabled&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Click create and wait for it to deploy&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a DNS record set
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DNS records provide information about the DNS zone.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Working on the private.contoso.com DNS zone, in the DNS Management blade, select + Recordsets.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdug2ojnv055rvnqckdq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdug2ojnv055rvnqckdq2.png" alt="recordsets" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There will be 2 A record sets there by default. Select +Add and configure like this: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: backend&lt;/li&gt;
&lt;li&gt;Type: A&lt;/li&gt;
&lt;li&gt;TTL: 1&lt;/li&gt;
&lt;li&gt;IP address: 10.1.1.5&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;his record set implies there is a virtual machine in app-vnet with a private IP address of 10.1.1.5.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ngyxshvj13inqnjko4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ngyxshvj13inqnjko4d.png" alt="private IP address" width="622" height="793"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In this exercise, we created a private DNS zone (private.contoso.com) in Azure, linked it to a virtual network (app-vnet) with auto-registration enabled, and added a DNS A record for a backend server (10.1.1.5). This setup allows private name resolution within the virtual network, supporting internal communication without exposing resources to the public internet.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>devops</category>
      <category>cloudskills</category>
    </item>
    <item>
      <title>Azure Applied Skills: Configure network routing</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Fri, 25 Apr 2025 23:15:44 +0000</pubDate>
      <link>https://dev.to/johnogbonna/azure-applied-skills-configure-network-routing-22he</link>
      <guid>https://dev.to/johnogbonna/azure-applied-skills-configure-network-routing-22he</guid>
      <description>&lt;p&gt;In this article series, I will be walking though the process of configuring secure access to workloads using Azure virtual networking. In this exercise, we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create and configure a route table.&lt;/li&gt;
&lt;li&gt;Link a route table to a subnet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will be building on what was done in &lt;a href="https://dev.to/johnogbonna/azure-applied-skills-creating-and-configuring-azure-firewall-mal"&gt;this exercise&lt;/a&gt; so make sure you have it done before attempting this one&lt;/p&gt;

&lt;h3&gt;
  
  
  Exercise 04: Configure network routing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;In the search box at the top of the portal, search for and select Firewalls.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select app-vnet-firewall (created in previous exercise)&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7qvmz3it2scmlij762a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7qvmz3it2scmlij762a.png" alt="Select app-vnet-firewall" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Overview and record the Private IP address.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxs9nlorqcesajxpm07cz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxs9nlorqcesajxpm07cz.png" alt="Private IP address" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search for and select Route tables&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click create route table&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferq812jyyjgw3gvj7f0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferq812jyyjgw3gvj7f0r.png" alt="Click create route table" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter these properties: &lt;br&gt;
Subscription: Select your subscription&lt;br&gt;
Resource group: Your resource group&lt;br&gt;
Region: East US&lt;br&gt;
Name: &lt;code&gt;app-vnet-firewall-rt&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Review + create and then select Create.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkvjakeft1w3mvro1kbx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkvjakeft1w3mvro1kbx.png" alt="select Create" width="794" height="789"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for the route table to deploy, then select Go to resource.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the portal, continue working with the route table, select &lt;code&gt;app-vnet-firewall-rt&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltm2zcmftjh2mwcog9jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltm2zcmftjh2mwcog9jq.png" alt="app-vnet-firewall-rt" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Subnets and then + Associate.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ckbguytssnlsq8rzb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ckbguytssnlsq8rzb3.png" alt="Select Subnets and then + Associate." width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select this configuration: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Virtual network: app-vnet&lt;/li&gt;
&lt;li&gt;Subnet: frontend
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum9tklhjpteqrpwl2pup.png" alt="frontend" width="800" height="671"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Again select + associate and add this configuration:&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Select this configuration: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Virtual network: app-vnet&lt;/li&gt;
&lt;li&gt;Subnet: backend
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felgzmxkjl1fha9mhjkf7.png" alt="backend" width="800" height="671"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Select &lt;code&gt;app-vnet-firewall-rt.&lt;/code&gt; in route tables&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In Settings, select Routes and then + Add.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Configure: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route name: outbound-firewall&lt;/li&gt;
&lt;li&gt;Destination type: IP addresses&lt;/li&gt;
&lt;li&gt;Destination IP addresses/CIDR range: 0.0.0.0/0&lt;/li&gt;
&lt;li&gt;Next hop type: Virtual appliance&lt;/li&gt;
&lt;li&gt;Next hop address: private IP address of the firewall
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ezx6hdhmarm9go04xxx.png" alt="private IP address of the firewall" width="800" height="671"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;You’ve now set up a route table and linked it to your subnets to direct all outbound traffic through the Azure Firewall. This ensures centralized control and enhanced security for your network traffic—an essential step in building a secure and well-managed Azure environment.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>networking</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Azure Applied Skills: Creating and configuring Azure Firewall</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Fri, 25 Apr 2025 04:06:08 +0000</pubDate>
      <link>https://dev.to/johnogbonna/azure-applied-skills-creating-and-configuring-azure-firewall-mal</link>
      <guid>https://dev.to/johnogbonna/azure-applied-skills-creating-and-configuring-azure-firewall-mal</guid>
      <description>&lt;p&gt;In this article series, I will be walking though the process of configuring secure access to workloads using Azure virtual networking. In this exercise, we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an Azure Firewall.&lt;/li&gt;
&lt;li&gt;Create and configure a firewall policy&lt;/li&gt;
&lt;li&gt;Create an application rule collection.&lt;/li&gt;
&lt;li&gt;Create a network rule collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will be building on what was done in &lt;a href="https://dev.to/johnogbonna/azure-applied-skills-creating-and-configuring-network-security-groups-1mgm/"&gt;this exercise&lt;/a&gt; so make sure you have it done&lt;/p&gt;

&lt;h3&gt;
  
  
  Exercise 03: Create and configure Azure Firewall
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Create Azure Firewall subnet in our existing virtual network
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Search for and select Virtual networks in the &lt;a href="//portal.azure.com"&gt;Azure portal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select app-vnet.&lt;/li&gt;
&lt;li&gt;Select Subnets.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select + Subnet.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwh15useyg5zjey6s3vt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwh15useyg5zjey6s3vt.png" alt="Select + Subnet" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter these settings&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subnet purpose: &lt;code&gt;Azure Firewall&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;AzureFirewallSubnet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Starting address: &lt;code&gt;10.1.63.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click add after entering these settings&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1zyxkrz989m8dbsa825.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1zyxkrz989m8dbsa825.png" alt="Click add after entering these settings" width="800" height="743"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Search for and select Firewall&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3bbg5439zuax6rz7tr3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3bbg5439zuax6rz7tr3.png" alt="Search for and select Firewall" width="800" height="739"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Select + create and enter these settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: app-vnet-firewall&lt;/li&gt;
&lt;li&gt;Resource group: your resource group&lt;/li&gt;
&lt;li&gt;Firewall SKU: Standard&lt;/li&gt;
&lt;li&gt;Firewall management: Use a Firewall Policy to manage this firewall&lt;/li&gt;
&lt;li&gt;Firewall policy: Add new&lt;/li&gt;
&lt;li&gt;Policy name: fw-policy&lt;/li&gt;
&lt;li&gt;Region: East US&lt;/li&gt;
&lt;li&gt;Policy Tier: Standard&lt;/li&gt;
&lt;li&gt;Choose a virtual network: Use existing&lt;/li&gt;
&lt;li&gt;Virtual network: app-vnet&lt;/li&gt;
&lt;li&gt;Public IP address Add new: fwpip&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click Review + create when configured&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click create&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94lma7s88xen45mm981y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94lma7s88xen45mm981y.png" alt="Click Review + create" width="800" height="1095"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Search for and select &lt;code&gt;Firewall Policies&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm15v81oopx9qmvi54n3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm15v81oopx9qmvi54n3d.png" alt="Search for and select  raw `Firewall Policies` endraw " width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Select &lt;code&gt;fw-policy&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frfo8vdeudn57ee6ipl9i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frfo8vdeudn57ee6ipl9i.png" alt="Select  raw `fw-policy` endraw " width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Under Rules, select Application rules and then Add a rule collection.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3ypevglmngcr0do4y44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3ypevglmngcr0do4y44.png" alt="Add a rule collection" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Configure the application rule and select add &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: app-vnet-fw-rule-collection&lt;/li&gt;
&lt;li&gt;Rule collection type: Application&lt;/li&gt;
&lt;li&gt;Priority: 200&lt;/li&gt;
&lt;li&gt;Rule collection action: Allow&lt;/li&gt;
&lt;li&gt;Rule collection group: DefaultApplicationRuleCollectionGroup&lt;/li&gt;
&lt;li&gt;Name: AllowAzurePipelines&lt;/li&gt;
&lt;li&gt;Source type: IP address&lt;/li&gt;
&lt;li&gt;Source: 10.1.0.0/23&lt;/li&gt;
&lt;li&gt;Protocol: https&lt;/li&gt;
&lt;li&gt;Destination type: FQDN&lt;/li&gt;
&lt;li&gt;Destination: dev.azure.com, azure.microsoft.com&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;![fw-policy](&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zvbjp86hf1ml1ump1zzo.png" rel="noopener noreferrer"&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zvbjp86hf1ml1ump1zzo.png&lt;/a&gt;&lt;br&gt;
The AllowAzurePipelines rule allows the web application to access Azure Pipelines. The rule allows the web application to access the Azure DevOps service and the Azure website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In Rules, select Network rules and then Add a rule collection.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24f53rcmvolwjuw41268.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24f53rcmvolwjuw41268.png" alt="Add a network collection" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select this configuration:&lt;br&gt;
Property: Value&lt;br&gt;
Name: app-vnet-fw-nrc-dns&lt;br&gt;
Rule collection type: Network&lt;br&gt;
Priority: 200&lt;br&gt;
Rule collection action: Allow&lt;br&gt;
Rule collection group: DefaultNetworkRuleCollectionGroup&lt;br&gt;
Rule: AllowDns&lt;br&gt;
Source: 10.1.0.0/23&lt;br&gt;
Protocol: UDP&lt;br&gt;
Destination ports: 53&lt;br&gt;
Destination addresses: 1.1.1.1, 1.0.0.1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click add when configured&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zxv6vw4581n4ioxjnm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zxv6vw4581n4ioxjnm4.png" alt="Click add when configured" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Verify the firewall and firewall policy status
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Search for and select &lt;code&gt;firewall&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;View the app-vnet-firewall and ensure the Provisioning state is Succeeded
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3x5eo1rbkfno53pmco98.png" alt="Provisioning state is Succeeded" width="800" height="505"&gt;
&lt;/li&gt;
&lt;li&gt;In the portal serach for and select Firewall policies.&lt;/li&gt;
&lt;li&gt;View the fw-policy and ensure the Provisioning state is Succeeded. 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk1haoq05mswm5phewi3.png" alt="Provisioning state is Succeeded" width="800" height="505"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔐 Azure Applied Skills: Exercise 03 – Azure Firewall Setup&lt;br&gt;
This exercise builds on previous work by adding an Azure Firewall to control outbound traffic in your virtual network.&lt;/p&gt;

&lt;p&gt;Key Steps:&lt;br&gt;
Create AzureFirewallSubnet in app-vnet.&lt;/p&gt;

&lt;p&gt;Deploy Azure Firewall with a new policy (fw-policy) and public IP (fwpip).&lt;/p&gt;

&lt;p&gt;Add Application Rule: Allow HTTPS access from 10.1.0.0/23 to dev.azure.com and azure.microsoft.com.&lt;/p&gt;

&lt;p&gt;Add Network Rule: Allow DNS (UDP port 53) to 1.1.1.1 and 1.0.0.1.&lt;/p&gt;

&lt;p&gt;Verify both firewall and policy show Provisioning state: Succeeded.&lt;/p&gt;

&lt;p&gt;✅ Result:&lt;br&gt;
A centralized firewall is now in place, enabling secure, policy-driven control of traffic in Azure.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Azure Applied Skills: Creating and configuring network security groups</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Tue, 22 Apr 2025 19:28:55 +0000</pubDate>
      <link>https://dev.to/johnogbonna/azure-applied-skills-creating-and-configuring-network-security-groups-1mgm</link>
      <guid>https://dev.to/johnogbonna/azure-applied-skills-creating-and-configuring-network-security-groups-1mgm</guid>
      <description>&lt;p&gt;In this article series, I will be walking though the process of configuring secure access to workloads using Azure virtual networking. In this exercise, we will put in place the virtual networks and subnets. This exercise builds on top of what we did previously &lt;a href="https://dev.to/johnogbonna/azure-applied-skills-create-and-configure-virtual-networks-45gi/edit"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will be walking through &lt;a href="https://microsoftlearning.github.io/Configure-secure-access-to-workloads-with-Azure-virtual-networking-services/Instructions/Labs/LAB_02_security_groups.html" rel="noopener noreferrer"&gt;this exercise&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  We require the network traffic in the app-vnet to be tightly controlled with these requirements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Frontend Subnet: Hosts internet-facing web servers. Assign them to an Application Security Group (ASG) for simplified management via their network interfaces.&lt;/li&gt;
&lt;li&gt;Backend Subnet: Contains database servers accessed by the frontend. Attach a Network Security Group (NSG) to control access from the web servers.&lt;/li&gt;
&lt;li&gt;Testing Setup: Deploy VM1 in the frontend and VM2 in the backend using the provided ARM template for Ubuntu servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tasks:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a network security group.&lt;/li&gt;
&lt;li&gt;Create network security group rules.&lt;/li&gt;
&lt;li&gt;Associate a network security group to a subnet.&lt;/li&gt;
&lt;li&gt;Create and use application security groups in network security group rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Exercise 02: Create and configure network security groups
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;This exercise requires the &lt;a href="https://dev.to/johnogbonna/azure-applied-skills-create-and-configure-virtual-networks-45gi/edit"&gt;previous one&lt;/a&gt; to be done&lt;/li&gt;
&lt;li&gt;Open the azure portal and click the button on the top right to launch the CLI. It will ask you to choose between bash or Powershell. select Powershell. 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfisnne21rl02r3lokfi.png" alt="select shell button" width="800" height="448"&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphpmvi6un80yhhkc4hqt.png" alt="select powershell" width="800" height="699"&gt;
&lt;/li&gt;
&lt;li&gt;Select no storage, your subscription, then apply
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dhn8fw3p1rc4skrwu05.png" alt="apply" width="800" height="248"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enter this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   $RGName = "your-rg"

   New-AzResourceGroupDeployment -ResourceGroupName $RGName -TemplateUri https://raw.githubusercontent.com/MicrosoftLearning/Configure-secure-access-to-workloads-with-Azure-virtual-networking-services/main/Instructions/Labs/azuredeploy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This will start up two virtual machines; vm1 and vm2 . Verify both are running in the &lt;a href="https://portal.azure.com/#view/Microsoft_Azure_ComputeHub/ComputeHubMenuBlade/~/virtualMachinesBrowse" rel="noopener noreferrer"&gt;virtual machines portal&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the portal, search for and select Application Security groups&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9thx2l1jwc7cgt273mcd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9thx2l1jwc7cgt273mcd.png" alt="Application Security groups" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select create and configure the security group as follows: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;subscription: Select your subscription&lt;/li&gt;
&lt;li&gt;Resource Group: Your resource group (same as the ones the vms)&lt;/li&gt;
&lt;li&gt;name: &lt;code&gt;app-frontend-asg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Region: &lt;code&gt;East US&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Select review + create then create &lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futwcdjjyelempp9sg7yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futwcdjjyelempp9sg7yg.png" alt="Select review + create then create" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;When the deployment is done, Search for and select &lt;code&gt;VM1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In the Networking blade, select Application security groups and then select Add application security groups. Then select &lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypl7y3pgnf60xxgemp4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypl7y3pgnf60xxgemp4q.png" alt="select Add application security groups" width="645" height="758"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Select app-frontend-asg and select Add application security groups&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Select &lt;code&gt;app-frontend-asg&lt;/code&gt; then select &lt;code&gt;Add&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tjdpndnbv56fhtc1966.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tjdpndnbv56fhtc1966.png" alt="add app-frontend-asg" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Create and Associate the Network Security Group
&lt;/h4&gt;

&lt;p&gt;The purpose of Network security groups (NSGs) is to secure network traffic in a virtual network.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the portal, search for and select &lt;code&gt;Network security group Select&lt;/code&gt;+Create`&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cnssh4wjuafeplmuxqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cnssh4wjuafeplmuxqs.png" alt="Select +Create" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter these configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subscription: your subscription &lt;/li&gt;
&lt;li&gt;Resource group: your resource group&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;app-vnet-nsg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Region: East US &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Select Review + create and then select Create.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1duna2qysclsaagtncq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1duna2qysclsaagtncq.png" alt="configure settings" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Associate the NSG with the app-vnet backend subnet
&lt;/h4&gt;

&lt;p&gt;NSGs can be associated with subnets and/or individual network interfaces attached to Azure virtual machines &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Navigate to the app-vnet-nsg resource.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farystm9yypv6iyffrpx7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farystm9yypv6iyffrpx7.png" alt="app-vnet-nsg resource" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Settings blade select Subnets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select + Associate&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs0x3ysmfy5m1xlehwau.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs0x3ysmfy5m1xlehwau.png" alt="Select + Associate" width="706" height="761"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select app-vnet and then the Backend subnet. Select OK.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu0kg8ut3sgzuurv2rj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu0kg8ut3sgzuurv2rj5.png" alt="Select OK" width="642" height="665"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Create Network Security Group rules
&lt;/h4&gt;

&lt;p&gt;An NSG uses security rules to filter inbound and outbound network traffic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;app-vnet-nsg&lt;/code&gt; resource, In the Settings blade, select Inbound security rules.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select + Add&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatle23bnup9nsik5vny9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatle23bnup9nsik5vny9.png" alt="Select + Add" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select this configuration: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: Any&lt;/li&gt;
&lt;li&gt;Source port ranges: *&lt;/li&gt;
&lt;li&gt;Destination: &lt;code&gt;Application Security group&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Destination application security group: &lt;code&gt;app-frontend-asg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Service: &lt;code&gt;SSH&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Action: &lt;code&gt;Allow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Priority: &lt;code&gt;100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;AllowSSH&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0hm9ke3kjx8kkb9h9bt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0hm9ke3kjx8kkb9h9bt.png" alt="Select this configuration" width="621" height="1127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click add&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;### Conclusion&lt;br&gt;
By completing this exercise, you've successfully configured secure access between frontend and backend workloads in Azure using Network Security Groups (NSGs) and Application Security Groups (ASGs). You’ve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deployed virtual machines across separate subnets for frontend and backend workloads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Created and assigned an ASG to the frontend VM to simplify rule management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Created an NSG, associated it with the backend subnet, and defined custom security rules to control access.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allowed SSH access from the frontend ASG to the backend subnet—enabling controlled, secure communication between tiers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup ensures that only explicitly defined network traffic—such as SSH from the frontend—is permitted, creating a strong security boundary within your virtual network. In the next exercise, you'll build on this foundation by implementing more advanced network features and controls.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>network</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Azure Applied Skills: Create and configure virtual networks</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Mon, 21 Apr 2025 23:33:11 +0000</pubDate>
      <link>https://dev.to/johnogbonna/azure-applied-skills-create-and-configure-virtual-networks-45gi</link>
      <guid>https://dev.to/johnogbonna/azure-applied-skills-create-and-configure-virtual-networks-45gi</guid>
      <description>&lt;p&gt;In this article series, I will be walking though the process of configuring secure access to workloads using Azure virtual networking. In this exercise, we will put in place the virtual networks and subnets&lt;br&gt;
We will be walking through &lt;a href="https://microsoftlearning.github.io/Configure-secure-access-to-workloads-with-Azure-virtual-networking-services/Instructions/Labs/LAB_01_virtual_networks.html" rel="noopener noreferrer"&gt;this exercise&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements:
&lt;/h2&gt;

&lt;p&gt;To do this exercise you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Azure subscription with Contributor RBAC role assigned. In this lab, when you are asked to create a resource, for any properties that are not specified, use the default value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exercise 01 - Create and configure virtual networks
&lt;/h2&gt;

&lt;p&gt;In this section, we will be walking through the steps detailed &lt;a href="https://microsoftlearning.github.io/Configure-secure-access-to-workloads-with-Azure-virtual-networking-services/Instructions/Labs/LAB_01_virtual_networks.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  This exercise will emulate the steps to migrate a web-based application to Azure. The first task is to put in place the virtual networks and subnets. Then we need to securely peer the virtual networks.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tasks:

&lt;ul&gt;
&lt;li&gt;Create a virtual network.&lt;/li&gt;
&lt;li&gt;Create a subnet.&lt;/li&gt;
&lt;li&gt;Configure vnet peering.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steps:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Sign into the &lt;a href="https://portal.azure.com" rel="noopener noreferrer"&gt;Azure portal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Search for and select Virtual Networks.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq715eexub855zcqc44px.png" alt="Search for and select Virtual Networks" width="800" height="435"&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click + Create&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwue69qktyeasplt0zdm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwue69qktyeasplt0zdm.png" alt="Click + Create" width="754" height="536"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter these values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subscription and Resource group: Your valid subscription and chosen resource group&lt;/li&gt;
&lt;li&gt;Virtual network name: app-vnet&lt;/li&gt;
&lt;li&gt;Region: (US) East US
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn3q0qfnz2pav8yvbeuvw.png" alt="configuration" width="800" height="688"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click IP addresses, then add IPV4 address space&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshortzwqe8gkj9mntz54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshortzwqe8gkj9mntz54.png" alt="add IPV4 address space" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Ensure that the new space starts at 10.1.0.0 and the size is /16&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Delete the default address space&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyevdtg0geo2ie5mda6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyevdtg0geo2ie5mda6q.png" alt="new space" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click add a subnet&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3kgbl6x9o6s5pvmu2g1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3kgbl6x9o6s5pvmu2g1.png" alt="Click add a subnet" width="800" height="824"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Call it &lt;code&gt;frontend&lt;/code&gt;, make sure the starting address is 10.1.0.0 and the size is 24&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click add when settings are configured &lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8vkrufxevxvkdu9ul44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8vkrufxevxvkdu9ul44.png" alt="add when settings are configured" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click Add subnet again in the same address space. Name it &lt;code&gt;backend&lt;/code&gt; also a size of 24. Make sure that the starting address is &lt;code&gt;10.1.1.0&lt;/code&gt;. Click add&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vojog0yunr7loyb6nqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vojog0yunr7loyb6nqc.png" alt="Click add" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Final setup&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix4lwy2a09ew4oj8l64h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix4lwy2a09ew4oj8l64h.png" alt="Final setup" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click review + create. Then create again and wait for deployment&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Search for and select virtual networks again. Click + create, like in the other steps.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Use these settings for the configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subscription and Resource Group: Same as previous virtual network&lt;/li&gt;
&lt;li&gt;Name: Virtual Network Name: &lt;code&gt;hub-vnet&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click IP addresses &lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm9xgcpnlrdfu07fx95x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm9xgcpnlrdfu07fx95x.png" alt="Click IP addresses" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click add subnet&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7uh7sj65i4owgvvqorm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7uh7sj65i4owgvvqorm.png" alt="Click add subnet" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click add subnet. Choose Azure Firewall as the subnet purpose, leave the default settings, and click Add.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjeqv81t2kugfswjsm3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjeqv81t2kugfswjsm3c.png" alt="Click add" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Remove the default space, click review + create&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpayziqj68734zzc4s2x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpayziqj68734zzc4s2x.png" alt="click review + create" width="800" height="765"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7jr1p74penyzvux4wzg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7jr1p74penyzvux4wzg.png" alt="Click Review + create" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the &lt;a href="https://portal.azure.com/?quickstart=true#browse/Microsoft.Network%2FvirtualNetworks" rel="noopener noreferrer"&gt;virtual networks&lt;/a&gt; portal, click app-vnet&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8t0ojxzeah8iim0tc7ky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8t0ojxzeah8iim0tc7ky.png" alt="virtual networks" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under peerings, select settings&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxwiucqy4s6ggm78vvuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxwiucqy4s6ggm78vvuy.png" alt="Under peerings, select settings" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click add&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use these settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Peering link name: &lt;code&gt;app-vnet-to-hub&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Subscription: same subscription used earlier&lt;/li&gt;
&lt;li&gt;Virtual network: &lt;code&gt;hub-vnet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Local virtual network peering link name: &lt;code&gt;hub-to-app-vnet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Leave the rest on defaults and click "Add"
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5vf5ch4mfn19budg5wg.png" alt="settings" width="800" height="1045"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Once the deployment completes, verify the Peering status is Connected (in the peering menu of app-vnet)&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwehler7716at0k1jizys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwehler7716at0k1jizys.png" alt="connected" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;At this point, you’ve successfully created and peered two virtual networks: app-vnet and hub-vnet. In the next exercise, we’ll start configuring security rules and deploying workloads within these networks.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>networking</category>
      <category>azure</category>
    </item>
    <item>
      <title>Docker Applied Skills: Containerize a Node.js application</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Thu, 20 Mar 2025 22:40:00 +0000</pubDate>
      <link>https://dev.to/johnogbonna/docker-applied-skills-containerize-a-nodejs-application-2goi</link>
      <guid>https://dev.to/johnogbonna/docker-applied-skills-containerize-a-nodejs-application-2goi</guid>
      <description>&lt;h2&gt;
  
  
  Docker Applied Skills: Containerize a Node.js Application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Docker is a powerful tool for containerizing applications, allowing developers to package their code and dependencies into lightweight, portable containers. In this guide, we’ll walk through the process of containerizing a &lt;strong&gt;Node.js application&lt;/strong&gt; using Docker. We will be following the steps outlined &lt;a href="https://docs.docker.com/guides/nodejs/containerize/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you will:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up a simple &lt;strong&gt;Node.js&lt;/strong&gt; application.
&lt;/li&gt;
&lt;li&gt;Write a &lt;strong&gt;Dockerfile&lt;/strong&gt; to containerize the application.
&lt;/li&gt;
&lt;li&gt;Build and run a &lt;strong&gt;Docker container&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Manage the container lifecycle using &lt;strong&gt;Docker commands&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why containerize apps?
&lt;/h3&gt;

&lt;p&gt;Containerizing apps offers several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistency: Ensures apps run the same across all environments (dev, test, prod).&lt;/li&gt;
&lt;li&gt;Portability: Easily deployable across different platforms and cloud services.&lt;/li&gt;
&lt;li&gt;Isolation: Prevents conflicts between app dependencies.&lt;/li&gt;
&lt;li&gt;Scalability: Easily scale applications based on demand.&lt;/li&gt;
&lt;li&gt;Faster Deployment: Containers are lightweight and start quickly.&lt;/li&gt;
&lt;li&gt;Version Control: Manage and roll back app versions easily.&lt;/li&gt;
&lt;li&gt;Resource Efficiency: Containers use fewer resources than VMs.&lt;/li&gt;
&lt;li&gt;Security: Isolates apps for better security and risk management.
Containerization streamlines development, deployment, and scalability, improving consistency, efficiency, and security.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you begin, ensure you have the following installed on your system:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js (LTS version)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/products/docker-desktop" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A git client. The examples in this section use a command-line based git client, but you can use any client.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s get started! &lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the Sample Application
&lt;/h3&gt;

&lt;p&gt;In your terminal/powershell, navigate to a folder where you'd like to store this sample application. To get the sample app, enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/docker/docker-nodejs-sample &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;docker-nodejs-sample
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To confirm that the files were brought in from git&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrwnd98o6gbfsj54uc5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrwnd98o6gbfsj54uc5r.png" alt="brought in from git" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Initialize Docker assets
&lt;/h3&gt;

&lt;p&gt;To create the Docker assets necessary to containerize the application, we can use Docker's built in Docker init feature to streamline the process. Conversely, we can also create these assets manually. In this example, we will use Docker init&lt;br&gt;
In the terminal enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Answer these prompts accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;What application platform does your project use? Node
What version of Node &lt;span class="k"&gt;do &lt;/span&gt;you want to use? &lt;span class="o"&gt;(&lt;/span&gt;ensure latest version ex. 21.5.0&lt;span class="o"&gt;)&lt;/span&gt;
Which package manager &lt;span class="k"&gt;do &lt;/span&gt;you want to use? npm
What &lt;span class="nb"&gt;command &lt;/span&gt;&lt;span class="k"&gt;do &lt;/span&gt;you want to use to start the app: node src/index.js
What port does your server listen on? 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvze3rse41wwofrv2v84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvze3rse41wwofrv2v84.png" alt="App setup" width="626" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter &lt;code&gt;ls&lt;/code&gt; in the terminal. You should see these files: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vpd4en2ixc7wzlazpes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vpd4en2ixc7wzlazpes.png" alt="You should see these files" width="631" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;docker-nodejs-sample&lt;/code&gt; directory, run the following command in a terminal to build the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you get an error similar to this, refer to the next step: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnw2x8ghan0girv9gi66i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnw2x8ghan0girv9gi66i.png" alt="error similar to this" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To bypass file errors, modify the &lt;code&gt;Dockerfile&lt;/code&gt;. Locate the CMD line and ensure that is says&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;CMD ["node", "src/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Like this: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcu3vgy1l3yhzpb3oh2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcu3vgy1l3yhzpb3oh2m.png" alt="cmd line" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;again and it should work. You should see a message in the terminal like this: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp33q8xqb5ycdep7c52vy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp33q8xqb5ycdep7c52vy.png" alt="like this" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open your browser and navigate to &lt;code&gt;http://localhost:3000/&lt;/code&gt;. You should see something like: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhd4gdmhp52y6stip4nyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhd4gdmhp52y6stip4nyk.png" alt="should see something like" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary:
&lt;/h2&gt;

&lt;p&gt;This guide walks through the process of containerizing a Node.js application using Docker. The steps include setting up a Node.js app, writing a Dockerfile to containerize it, building and running the Docker container, and managing the container lifecycle with Docker commands.&lt;/p&gt;

&lt;p&gt;Key steps include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Containerization benefits: Docker ensures consistency, portability, isolation, scalability, faster deployment, and better security.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting the Sample App: Clone the sample node.js app from GitHub.&lt;br&gt;
Initialize Docker Assets: Use Docker’s docker init to streamline the process of creating necessary Docker files for the project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build and Run the App: Use docker compose up --build to build the containerized app and fix any errors that might arise during the process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The node.js application will be successfully containerized and running locally using Docker.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>webdev</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Getting Started with C# and .NET Core: Build a Web and Console App Using VS Code</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Thu, 20 Mar 2025 04:39:55 +0000</pubDate>
      <link>https://dev.to/johnogbonna/getting-started-with-c-and-net-core-build-a-web-and-console-app-using-vs-code-5ebg</link>
      <guid>https://dev.to/johnogbonna/getting-started-with-c-and-net-core-build-a-web-and-console-app-using-vs-code-5ebg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;C# is a powerful, modern programming language developed by Microsoft, widely used for building web applications, desktop software, cloud services, and more. In this guide, we will walk through creating both a console application and a web application using .NET Core and Visual Studio Code.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up your development environment with .NET Core and VS Code&lt;/li&gt;
&lt;li&gt;Begin the setup of a web application using ASP.NET Core &lt;/li&gt;
&lt;li&gt;Create a simple C# console application using AI generated code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Install .NET SDK from &lt;a href="//dotnet.microsoft.com"&gt;here&lt;/a&gt; or through VS code using &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-runtime" rel="noopener noreferrer"&gt;this extension&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Download and install Visual Studio Code from code.visualstudio.com&lt;br&gt;
Let's dive in!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In your local machine, create a folder where we will make these apps. Open the terminal, VS code terminal or powershell and navigate to this folder&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the command line, enter:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a response similar to below. If get an error, you need to verify that .NET is properly installed and set up on your local machine&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6q1nbzcb48m141inuwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6q1nbzcb48m141inuwy.png" alt="properly installed" width="401" height="71"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating and Running the Web App
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Open the folder in VS code. Navigate to the terminal. Enter:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new web &lt;span class="nt"&gt;-n&lt;/span&gt; MyWebApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Breakdown:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dotnet new → Creates a new .NET project.&lt;/li&gt;
&lt;li&gt;web → Uses the ASP.NET Core minimal web app template.&lt;/li&gt;
&lt;li&gt;n MyWebApp → Names the project MyWebApp and creates a folder for it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Open &lt;code&gt;Program.cs&lt;/code&gt; and look at the code inside.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In the terminal navigate to the MyWebApp folder, enter&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dontnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Look in the terminal. Hold control (or command for Mac) and click where it gives the localhost link. This will open the app in your web browser&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewza16841jamwbi6g9xh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewza16841jamwbi6g9xh.png" alt="web browser" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the browser you will see this: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cjct3uv4gz0yj1fj8u2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cjct3uv4gz0yj1fj8u2.png" alt="In the browser you will see this" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go back to VS code and press Control + c to stop the server. Look at the code in Program.cs. Notice this part in the code: &lt;br&gt;
&lt;code&gt;app.MapGet("/", () =&amp;gt; "Hello World!");&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This controls what is displayed. Try changing the displayed message. Example:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;app.MapGet&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;, &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"I'm learning C#!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the terminal, enter
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet build
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Explanation:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dotnet build&lt;/code&gt;: Compiles the C# code and checks for errors. Then generates an optimized binary (DLL file) inside the bin/ folder.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dotnet run&lt;/code&gt;: Runs the compiled application on a web server that listens for requests.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Open the new localhost&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0clzfkibpe1zrdq66cb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0clzfkibpe1zrdq66cb.png" alt="Open the new localhost" width="800" height="747"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;You should see the new message&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qet01iatu7expmfup6y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qet01iatu7expmfup6y.png" alt="You should see the new message" width="444" height="164"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating and Running the Console App
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;control + c&lt;/code&gt; in the terminal to stop the web app server. &lt;/li&gt;
&lt;li&gt;Navigate out of the web app folder in the terminal using the command &lt;code&gt;cd ..&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;To set up the files needed for a console app, enter the comand:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; MyConsoleApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This will create a folder called My console app with the necessary files to run a console app. Open the folder and navigate to &lt;code&gt;Program.cs&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a7v5sadb4e53mswigmi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a7v5sadb4e53mswigmi.png" alt="Program.cs" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the terminal, navigate to the console app folder then enter&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This will build and run the app. Note the output in the terminal:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3bz0wzzsizb5tpb7ijd.png" alt="output in the terminal" width="363" height="277"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, console apps primarily run in the terminal. &lt;/p&gt;

&lt;h3&gt;
  
  
  Generating and running sample code in the console
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Open a generative AI of your choice. Enter a prompt similar to this: &lt;code&gt;create a snake game that runs in c sharp console&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replace the contents of &lt;code&gt;Program.cs&lt;/code&gt; with the generated code and save the file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the terminal enter :&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet build
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You can now use the app in the console
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9nch6h5zpnh8kfdqhwl.png" alt="app in the console" width="371" height="306"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;This article introduces C# development with .NET Core using Visual Studio Code. We explore the basics of creating and running both console and web applications using .NET.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;:&lt;br&gt;
✅ Setting up the environment – using .NET SDK and VS Code&lt;br&gt;
✅ Setting up a C# web application – Creating a minimal ASP.NET web app&lt;br&gt;
✅ Setting up a C# console app – Generating and running a Snake game that runs in the terminal&lt;br&gt;
✅ Modifying and running the app – Editing Program.cs and using &lt;code&gt;dotnet build&lt;/code&gt; &amp;amp; &lt;code&gt;dotnet run&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This hands-on approach demonstrates how C# and .NET Core can be used to create both interactive console and web applications, making it a powerful tool for development. 🚀&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Git Applied Skills: Using My Portfolio Template to Launch Your Own Portfolio Site</title>
      <dc:creator>John Ogbonna</dc:creator>
      <pubDate>Tue, 18 Mar 2025 21:21:45 +0000</pubDate>
      <link>https://dev.to/johnogbonna/git-applied-skills-using-my-portfolio-template-to-launch-your-own-portfolio-site-44a9</link>
      <guid>https://dev.to/johnogbonna/git-applied-skills-using-my-portfolio-template-to-launch-your-own-portfolio-site-44a9</guid>
      <description>&lt;p&gt;Creating a personal portfolio can be overwhelming, but with GitHub templates, you can set up your own site in minutes! In this guide, we'll use my &lt;strong&gt;GitHub portfolio template&lt;/strong&gt; made for Software and Cloud developers to create a new repository, clone it, and customize it to fit your style.  &lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you'll have:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A GitHub repository based on my template
&lt;/li&gt;
&lt;li&gt;A cloned version on your local machine
&lt;/li&gt;
&lt;li&gt;A ready-to-customize portfolio site
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let's get started!
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🛠️ &lt;strong&gt;What You Need&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before we begin, make sure you have the following:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Git Command Line (CLI)&lt;/strong&gt;
You'll need Git installed to clone and manage your repository. If you don’t have Git yet, download and install it from:
🔗 &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git Download Page&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To check if Git is installed, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A text editor (VS Code highly recommended)&lt;/strong&gt;
A code editor is essential for modifying your portfolio. I recommend VS Code, which you can download here:
🔗 &lt;a href="https://code.visualstudio.com/Download" rel="noopener noreferrer"&gt;VS Code Download&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have these set up, you're ready to create your portfolio repository!&lt;/p&gt;

&lt;h3&gt;
  
  
  Making a Repository From the Template
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In your browser, go to my &lt;a href="https://github.com/JohnOgbonna/developer_portfolio_template" rel="noopener noreferrer"&gt;portfolio template repository&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on &lt;code&gt;Use this template&lt;/code&gt; and then &lt;code&gt;Create a new repository&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhsxgu8vk7a7wllwkugn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhsxgu8vk7a7wllwkugn.png" alt="Create a new repository" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give the repo a name&lt;/li&gt;
&lt;li&gt;Give the repo a description&lt;/li&gt;
&lt;li&gt;Select public or private&lt;/li&gt;
&lt;li&gt;Click create repository
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgdqlcjfwwdsxizpibew.png" alt="Create a new repository" width="800" height="835"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Open your terminal and navigate to a suitable environment to download the template. Then enter this command:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/JohnOgbonna/my_dev_portfolio.git 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Open the cloned folder in VS code
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1o37onhz79lcbefyhfzt.png" alt="Open the folder in VS code" width="800" height="648"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Editing Your site locally
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Open the &lt;code&gt;README.md&lt;/code&gt; to view the instructions on how to edit the site.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1pohp8igjhhefqsew0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1pohp8igjhhefqsew0e.png" alt="how to edit the site" width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edit the data.json file accordingly. Install &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" rel="noopener noreferrer"&gt;VS Code live server&lt;/a&gt;. Open &lt;code&gt;index.html&lt;/code&gt; in the editor, On the bottom right, click go live to view the site locally]&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdafcol23w8rxfyv4tuve.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdafcol23w8rxfyv4tuve.png" alt="Click go live" width="800" height="912"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The site will open in the browser. As you make changes in &lt;code&gt;data.json&lt;/code&gt;, you will see them in the browser&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A mentioned in the &lt;code&gt;README.md&lt;/code&gt;, To change the content displayed, simply edit the &lt;code&gt;data.json&lt;/code&gt;. Add a profile picture to the root directory and call it &lt;code&gt;profilepic.png&lt;/code&gt;. Remove the default one (unless you'd like an AI generated image of me as your portfolio profile picture). here is an example of the "skills" object I modified:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"EC2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"S3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Lambda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"DynamoDB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Amplify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Api Gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"SES"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Azure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Azure Functions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Blob Storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"App Service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Azure DevOps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Azure Virtual Machines"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Google Cloud"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Cloud Functions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Cloud Storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"Firebase"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Adjust the information for yourself accordingly. Follow the instructions and make sure your information follows the same pattern in order to avoid compilation errors when building the site. &lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store article photos and project photos in the correct folder and reference them accordingly in &lt;code&gt;data.json&lt;/code&gt;. Notice that these can also be web URLs&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxflax3p8nkdpxwzcvuqm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxflax3p8nkdpxwzcvuqm.png" alt="Example" width="696" height="862"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I changed my theme to &lt;code&gt;light-red&lt;/code&gt; with the option to toggle dark mode. See a list of allowed theme setups in the &lt;code&gt;README.md&lt;/code&gt;. Adjust these settings as you wish&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flkgqa99iwr1mjrld883h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flkgqa99iwr1mjrld883h.png" alt="theme settings" width="324" height="74"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;My modified site: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o5sucf71vuogi5n4ym7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o5sucf71vuogi5n4ym7.png" alt="My modified site" width="800" height="817"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you are experienced in web development, feel free to modify ay of the other files and customize fully to get your desired look and feel. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Committing and pushing to Github
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Once you are satisfied with your modifications, it's time to commit and push the changes. In the terminal enter:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This will list all the changes you made to the base template&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then enter&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This will add mark changes made to be committed&lt;/li&gt;
&lt;li&gt;Then enter the following command, replacing "Commit message" with a meaningful description of your update::
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Commit message"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will now stage the changes to be pushed to the repository on git hub, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjpmvc8fdybk7f2w5ylh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjpmvc8fdybk7f2w5ylh.png" alt="committing changes" width="635" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your changes are now live in your GitHub repository in the main branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deploying live site with github pages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the Github repository in the browser&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click settings &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby49uavpprqy39fdbtlo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby49uavpprqy39fdbtlo.png" alt="Click settings" width="635" height="475"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click pages&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flal4bsw6b7nyg1ibbjtr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flal4bsw6b7nyg1ibbjtr.png" alt="Click pages" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select deploy from branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select main&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select save &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fid2sqsx6yfthi74imdel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fid2sqsx6yfthi74imdel.png" alt="set up pages" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait a few moments and the link will appear&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsq3k0a442n56ifxekvrm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsq3k0a442n56ifxekvrm.png" alt="link" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your site is now live and accessible from a link&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnx5secm3l1eazax3o62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnx5secm3l1eazax3o62.png" alt="Live site" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>git</category>
      <category>portfolio</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
