<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[DevOps and Beyond]]></title><description><![CDATA[DevOps and Beyond]]></description><link>https://tech.withharshit.com</link><generator>RSS for Node</generator><lastBuildDate>Sat, 02 May 2026 05:04:43 GMT</lastBuildDate><atom:link href="https://tech.withharshit.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building the Automation Engine: DevOps the Right Way]]></title><description><![CDATA[Building the Automation Engine: DevOps the Right Way
Modern software teams are under constant pressure to ship faster, stay secure, and keep systems reliable — all at once. But many teams end up with ]]></description><link>https://tech.withharshit.com/building-the-automation-engine-devops-the-right-way</link><guid isPermaLink="true">https://tech.withharshit.com/building-the-automation-engine-devops-the-right-way</guid><category><![CDATA[Devops]]></category><category><![CDATA[Infrastructure as code]]></category><category><![CDATA[cicd]]></category><category><![CDATA[deployment]]></category><category><![CDATA[best practices]]></category><dc:creator><![CDATA[Harshit Jain]]></dc:creator><pubDate>Thu, 30 Oct 2025 19:42:07 GMT</pubDate><content:encoded><![CDATA[<h1>Building the Automation Engine: DevOps the Right Way</h1>
<p>Modern software teams are under constant pressure to ship faster, stay secure, and keep systems reliable — all at once. But many teams end up with a patchwork of tools, copied YAML files, and pipelines nobody wants to touch.</p>
<p>This article walks you through the core principles and tools that make up a mature, well-structured DevOps setup — from GitOps fundamentals to security, secrets management, and getting the most out of every tool you adopt.</p>
<hr />
<h2>1. Why GitOps Instead of Traditional CI/CD?</h2>
<p>In a microservices world, where you might be managing dozens of small services, traditional CI/CD pipelines can quickly become complex. Each pipeline is responsible for pushing changes to the cluster, which makes it harder to keep track of what's running and even harder to roll back safely.</p>
<p>GitOps takes a different approach. Instead of pipelines pushing changes, your cluster <strong>pulls</strong> the desired state from Git. Everything — from app versions to configurations — lives in Git, making it your <strong>single source of truth</strong>.</p>
<p>Tools like ArgoCD and Flux don't just apply manifests to the cluster; they <strong>continuously watch</strong> the cluster for any drift. If someone changes something manually, these tools automatically fix it by syncing back to what's defined in Git. They even clean up old resources that no longer exist in Git — something traditional push-based pipelines can't easily handle.</p>
<hr />
<h2>2. Flux vs ArgoCD — Choosing the Right GitOps Tool</h2>
<p>Once you've decided to adopt GitOps, the next question is: <strong>which tool?</strong> Both Flux and ArgoCD are excellent choices, but they have different strengths.</p>
<h3>🎯 Installation and Setup</h3>
<ul>
<li><p><strong>Flux</strong> is lightweight and integrates directly with Kubernetes with minimal setup.</p>
</li>
<li><p><strong>ArgoCD</strong> comes with a server and web UI, which takes a bit more configuration but gives you a full visual control plane.</p>
</li>
</ul>
<h3>🖥️ UI vs CLI-First Approach</h3>
<ul>
<li><p><strong>ArgoCD</strong> has a rich web dashboard where you can track apps, see sync status, and monitor health — great for teams who prefer visuals.</p>
</li>
<li><p><strong>Flux</strong> has no built-in UI; it's CLI- and Git-first, making it ideal for automation-focused environments.</p>
</li>
</ul>
<h3>💬 Notifications and Integrations</h3>
<ul>
<li><p><strong>ArgoCD</strong> includes built-in notifications and webhooks that work with Slack, Jira, and more.</p>
</li>
<li><p><strong>Flux</strong> supports notifications through a separate Notification Controller, offering flexibility at the cost of some extra setup.</p>
</li>
</ul>
<h3>🧩 Helm Release Management</h3>
<p>This is where the two tools differ most significantly.</p>
<p><strong>Flux</strong> manages Helm as a first-class citizen through its Helm Controller, which continuously watches your Git repo and manages releases end-to-end. You define two main resources in Git:</p>
<ul>
<li><p>A <code>HelmRepository</code> — where your chart lives</p>
</li>
<li><p>A <code>HelmRelease</code> — what version, values, and settings to apply</p>
</li>
</ul>
<pre><code class="language-yaml"># helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: myapp
  namespace: production
spec:
  interval: 1m
  chart:
    spec:
      chart: myapp
      version: 1.2.0
      sourceRef:
        kind: HelmRepository
        name: myrepo
  values:
    image:
      tag: v1.0.0
</code></pre>
<p>Update the image tag to <code>v1.1.0</code> and push it to Git — Flux automatically detects the change, runs <code>helm upgrade</code>, and syncs the cluster. No manual command needed. If someone manually changes something in the cluster, Flux notices the drift and heals it back to Git's state.</p>
<p><strong>ArgoCD</strong>, on the other hand, treats Helm more like a template engine. It runs <code>helm template</code> behind the scenes and applies the resulting manifests to your cluster. It doesn't manage Helm releases natively — no <code>helm upgrade</code> hooks, no rollback history, no automatic resync of Helm releases.</p>
<blockquote>
<p><strong>Simple takeaway:</strong> With Flux, Helm behaves like it should — versioned, continuously managed, rollback-friendly, and self-healing. With ArgoCD, Helm is primarily used to render YAML, not to manage a living Helm release.</p>
</blockquote>
<hr />
<h2>3. Why Use Helm at All — The Mental Model</h2>
<p>Before going deeper into GitOps tooling, it's worth understanding why Helm itself is valuable.</p>
<p>Helm bundles all those Kubernetes YAML files into a single <strong>chart</strong> and lets you customize them through a simple <code>values.yaml</code> file. Instead of manually editing dozens of manifests, you tweak a few values in one place.</p>
<p>For example, deploying Prometheus from scratch would require hunting for 40–50 manifest files covering deployments, services, RBACs, and CRDs. With Helm, it's a single command:</p>
<pre><code class="language-bash">helm install prometheus prometheus-community/kube-prometheus-stack
</code></pre>
<p>When deploying tools like Prometheus, Grafana, or Traefik, Helm gives you:</p>
<ul>
<li><p>✅ Tested, community-maintained charts with sensible defaults</p>
</li>
<li><p>✅ No need to write or maintain custom manifests from scratch</p>
</li>
<li><p>✅ The ability to override only what you need (storage size, ingress host, alert rules, etc.)</p>
</li>
<li><p>✅ Easy upgrades — just bump the chart version, no YAML chaos</p>
</li>
</ul>
<hr />
<h2>4. Managing Secrets Safely — Sealed Secrets</h2>
<p>Kubernetes Secrets are meant to store sensitive information like passwords, tokens, and API keys. But there's a critical problem — they're only <strong>base64-encoded</strong>, not encrypted. Anyone with access to your cluster or Git repo can decode them instantly.</p>
<h3>Cloud vs On-Premise — A Different Problem</h3>
<p>If you're running on a managed cloud Kubernetes service, you likely have access to a native secret management solution:</p>
<ul>
<li><p><strong>AWS EKS</strong> → AWS Secrets Manager / Parameter Store (via External Secrets Operator)</p>
</li>
<li><p><strong>Azure AKS</strong> → Azure Key Vault (via CSI driver or External Secrets Operator)</p>
</li>
<li><p><strong>GCP GKE</strong> → Google Secret Manager (via External Secrets Operator)</p>
</li>
</ul>
<p>These cloud-native solutions are tightly integrated with IAM, support automatic rotation, and offload the encryption burden entirely to the cloud provider. If you're on a managed cloud cluster, <strong>use these first</strong> — they're purpose-built for this.</p>
<p>But what if you're running Kubernetes <strong>on-premise</strong> — on bare metal, VMware, or private infrastructure? You don't have Key Vault. You don't have IAM roles. You're on your own.</p>
<p>This is exactly where <strong>Sealed Secrets</strong> becomes the practical, battle-tested answer.</p>
<hr />
<h3>What Is Sealed Secrets?</h3>
<p>Sealed Secrets is an open-source project by Bitnami that lets you safely commit encrypted secrets to Git — the GitOps way. It works entirely within your cluster, with no dependency on any cloud provider.</p>
<h3>How Sealed Secrets Work</h3>
<p>Sealed Secrets rely on a <strong>public–private key pair</strong>:</p>
<ul>
<li><p>A <strong>Sealed Secrets Controller</strong> runs inside your Kubernetes cluster and holds the <strong>private key</strong>, which never leaves the cluster.</p>
</li>
<li><p>You use the <strong>public key</strong> on your local machine to encrypt secrets before committing them to Git.</p>
</li>
</ul>
<p>Think of it like a lockbox: you lock the secret with the public key (safe to share), and only the controller inside the cluster can unlock it with the private key (kept secret). Even if someone sees your encrypted secret in Git, they cannot decrypt it without direct access to your cluster.</p>
<h3>Using <code>kubeseal</code></h3>
<p>First, install the Sealed Secrets Controller in your cluster:</p>
<pre><code class="language-bash">helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
</code></pre>
<p>Then fetch the public key from your running controller:</p>
<pre><code class="language-bash">kubeseal --fetch-cert \
  --controller-name=sealed-secrets \
  --controller-namespace=kube-system \
  &gt; pub-cert.pem
</code></pre>
<blockquote>
<p>💡 <strong>On-premise tip:</strong> Save this <code>pub-cert.pem</code> somewhere accessible to your team (like an internal artifact repo or shared drive). Developers need it to seal secrets locally without needing direct cluster access every time.</p>
</blockquote>
<p>Now create a normal Kubernetes Secret locally (without applying it to the cluster):</p>
<pre><code class="language-bash">kubectl create secret generic my-secret \
  --from-literal=password=MyS3cretP@ss \
  --dry-run=client -o yaml &gt; my-secret.yaml
</code></pre>
<p>Then encrypt it using the <code>kubeseal</code> CLI, using the cert you fetched:</p>
<pre><code class="language-bash">kubeseal --format yaml \
  --cert pub-cert.pem \
  &lt; my-secret.yaml &gt; my-sealedsecret.yaml
</code></pre>
<p>This produces a <code>SealedSecret</code> file that looks like gibberish — totally safe to commit to Git.</p>
<p>Once you push it through your GitOps pipeline and it lands on the cluster:</p>
<pre><code class="language-bash">kubectl apply -f my-sealedsecret.yaml
</code></pre>
<p>The Sealed Secrets Controller automatically decrypts it back into a normal Kubernetes Secret inside the cluster.</p>
<hr />
<h3>Key Rotation</h3>
<p>The controller automatically rotates its key pair every <strong>30 days</strong> by default (configurable). When a new key pair is created, old ones are retained so existing SealedSecrets can still be decrypted during the transition.</p>
<blockquote>
<p>⚠️ <strong>On-premise critical warning:</strong> If your cluster is destroyed and rebuilt (common in on-premise DR or cluster migration scenarios), the private key is lost with it. You must <strong>back up the controller's private key</strong> manually and restore it after rebuilding, otherwise all your SealedSecrets become permanently unreadable.</p>
</blockquote>
<p>Back up the key like this:</p>
<pre><code class="language-bash">kubectl get secret -n kube-system \
  -l sealedsecrets.bitnami.com/sealed-secrets-key \
  -o yaml &gt; sealed-secrets-master-key-backup.yaml
</code></pre>
<p>Store this backup somewhere <strong>secure and offline</strong> — not in Git. Treat it like your cluster's root password.</p>
<hr />
<h3>On-Premise Alternatives Worth Knowing</h3>
<p>Sealed Secrets is the most common approach for on-premise GitOps, but it's not the only option:</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th>How it works</th>
<th>Best for</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Sealed Secrets</strong></td>
<td>Encrypts secrets with a cluster-held key; stored in Git</td>
<td>Pure on-prem GitOps, simple setup</td>
</tr>
<tr>
<td><strong>HashiCorp Vault</strong></td>
<td>Full secret management platform; apps fetch secrets at runtime</td>
<td>Large teams, fine-grained access control, secret leasing</td>
</tr>
<tr>
<td><strong>SOPS + Age/GPG</strong></td>
<td>Encrypts files using age or GPG keys; works with Flux natively</td>
<td>Teams already using GPG/age key infrastructure</td>
</tr>
<tr>
<td><strong>External Secrets Operator</strong></td>
<td>Syncs secrets from an external store (Vault, AWS, Azure) into K8s</td>
<td>Hybrid setups where Vault is already in place</td>
</tr>
</tbody></table>
<p>For most <strong>small to mid-sized on-premise clusters</strong>, Sealed Secrets hits the sweet spot — low operational overhead, no extra infrastructure, and it works natively with any GitOps tool like Flux or ArgoCD.</p>
<p>If your team is larger or needs features like dynamic secrets, secret leasing, or fine-grained audit logs, <strong>HashiCorp Vault</strong> is worth the investment despite its steeper setup curve.</p>
<hr />
<h2>5. Keeping Pipelines Clean — Templatization and Structure</h2>
<p>As projects grow, CI/CD pipelines tend to become a dumping ground — full of copied YAMLs, inconsistent variable names, and undocumented scripts. Application code gets reviewed carefully; pipeline code rarely does, even though it's just as critical.</p>
<p><strong>Templatization</strong> keeps pipelines modular, readable, and reusable across projects and environments.</p>
<h3>A Clean Folder Structure</h3>
<p>Here's a practical way to organize your CI/CD templates (GitHub Actions example):</p>
<pre><code class="language-plaintext">.
├── .github/
│   └── workflows/
│       ├── build.yml
│       ├── deploy.yml
│       └── templates/
│           ├── steps/
│           │   ├── build-node-app.yml
│           │   ├── build-dotnet-app.yml
│           │   └── push-to-acr.yml
│           ├── jobs/
│           │   ├── lint-and-test.yml
│           │   ├── docker-build.yml
│           │   └── deploy-to-aks.yml
│           └── common/
│               ├── variables.yml
│               └── environment.yml
└── scripts/
    ├── cleanup.sh
    └── versioning.ps1
</code></pre>
<h3>Best Practices for Templatization</h3>
<p><strong>Use templates for repeated steps</strong> — Instead of defining the same "Build and Push Image" job in multiple pipelines, create one template and call it wherever needed:</p>
<pre><code class="language-yaml">jobs:
  - template: templates/jobs/docker-build.yml
    parameters:
      imageName: myapp
      tag: $(Build.BuildId)
</code></pre>
<p><strong>Keep environment configs separate</strong> — Define environment-specific variables in isolated files:</p>
<pre><code class="language-yaml">variables:
  - template: templates/common/environment.yml
</code></pre>
<p><strong>Follow a consistent naming convention</strong> — Use clear, scoped names:</p>
<pre><code class="language-plaintext">env.DATABASE_URL
build.IMAGE_TAG
deploy.NAMESPACE
</code></pre>
<p>Avoid cryptic names like <code>dbUrl</code> or <code>imgTg</code>.</p>
<p><strong>Keep logic in scripts, not YAML</strong> — YAML is for configuration. Complex logic belongs in versioned shell or PowerShell scripts under <code>/scripts</code>, called from your pipeline.</p>
<p><strong>Add comments like production code</strong> — Explain <em>why</em> a step exists, not just <em>what</em> it does.</p>
<p><strong>Lint your pipelines</strong> — Use tools like <code>actionlint</code> (GitHub) or <code>yamllint</code> to catch issues early.</p>
<p><strong>Apply DRY (Don't Repeat Yourself)</strong> — If three or more lines repeat across multiple pipelines, it's time to extract a template.</p>
<blockquote>
<p>A messy pipeline is easy to write but painful to maintain. When something breaks at 2 AM, nobody wants to debug 600 lines of uncommented YAML.</p>
</blockquote>
<hr />
<h2>6. Security from Day One — The DevSecOps Mindset</h2>
<p>In most projects, security is treated as something to "add later." But by the time a product is running in production, fixing vulnerabilities costs significantly more time, effort, and money than preventing them early.</p>
<p><strong>DevSecOps</strong> means building security into every stage of your pipeline — from code commits to deployments. This includes:</p>
<ul>
<li><p>🔍 <strong>Secret scanning</strong> — catch accidentally committed credentials before they're pushed</p>
</li>
<li><p>📦 <strong>Dependency checks</strong> — flag known vulnerabilities in your libraries</p>
</li>
<li><p>🐳 <strong>Container vulnerability scans</strong> — inspect your Docker images before deployment</p>
</li>
<li><p>🏗️ <strong>IaC validation</strong> — lint and audit your Terraform, Helm charts, and Kubernetes manifests</p>
</li>
</ul>
<p>Adding these checks directly into your CI/CD pipeline turns security into a natural part of your workflow, not a separate audit phase.</p>
<blockquote>
<p>Security isn't a phase at the end of the project — it's part of how you build, test, and ship from day one. This approach saves time, prevents costly breaches, and builds trust in what you deliver.</p>
</blockquote>
<p>For a deeper look, check out: <strong>Essential Tools for a Secure DevSecOps Pipeline</strong></p>
<hr />
<h2>7. Don't Just Add Tools — Use Their Full Potential</h2>
<p>Many teams proudly say, <em>"We've added Flux, Traefik, Prometheus..."</em> — but just installing tools doesn't mean you're getting their real value. Tools are powerful only when you understand and leverage their automation and integration features fully.</p>
<h3>Example 1: Flux — Automated Image Deployment</h3>
<p>Flux isn't just a GitOps sync tool — it can also <strong>automate image updates</strong>.</p>
<p>Say you push a new Docker image <code>myapp:v2.0.0</code> to your container registry. Instead of manually updating your <code>HelmRelease</code> or <code>values.yaml</code>, Flux can automatically:</p>
<ol>
<li><p>Detect the new image tag from the registry</p>
</li>
<li><p>Update the image version in Git</p>
</li>
<li><p>Commit that change automatically</p>
</li>
<li><p>Sync it to your cluster</p>
</li>
</ol>
<p>Your entire deployment pipeline becomes <strong>self-updating</strong> — zero manual changes required.</p>
<p>Most teams stop at GitOps sync and never enable image automation, which is one of Flux's most powerful features.</p>
<h3>Example 2: Traefik — Built-in Observability</h3>
<p>Traefik isn't just an ingress controller — it ships with a <strong>built-in dashboard and Prometheus metrics</strong> showing real-time traffic, routes, SSL certificate status, and backend health.</p>
<p>In lightweight clusters like k3s, Traefik is already included by default. By simply enabling its dashboard and metrics, you can:</p>
<ul>
<li><p>Monitor all ingress routes visually</p>
</li>
<li><p>Debug traffic flow without tailing endless logs</p>
</li>
<li><p>Spot unhealthy services at a glance</p>
</li>
</ul>
<p>Most teams install Traefik and never explore these features — leaving easy visibility and debugging power on the table.</p>
<h3>The Bigger Lesson</h3>
<p>Every tool you adopt has capabilities that can save hours of manual work, prevent mistakes, and improve reliability. Taking the time to explore them fully is what separates a team that <em>uses</em> DevOps tools from one that <em>leverages</em> them.</p>
<blockquote>
<p>Don't stop at installation. Learn what your tools can really do — that's where DevOps transforms from scripts and dashboards into true automation and reliability.</p>
</blockquote>
<hr />
<h2>Closing Thoughts</h2>
<p>A mature DevOps setup isn't built overnight, and it's not just about the tools you pick. It's about the <strong>principles</strong> behind them — treating Git as the source of truth, keeping secrets safe, writing pipelines like production code, and weaving security in from the start.</p>
<p>When you combine GitOps with structured pipelines, proper secrets management, and a mindset of using tools to their full potential, you stop firefighting and start building systems that are genuinely reliable, auditable, and easy to hand off to any engineer on your team.</p>
<p><strong>That's DevOps done right.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Building a Robust DevSecOps Pipeline: Essential Open-Source Security Tools]]></title><description><![CDATA[Security can no longer be an afterthought in modern software development. DevSecOps integrates security into every phase of the development pipeline, catching vulnerabilities early when they're cheapest to fix. The best part? You don't need expensive...]]></description><link>https://tech.withharshit.com/building-a-robust-devsecops-pipeline-essential-open-source-security-tools</link><guid isPermaLink="true">https://tech.withharshit.com/building-a-robust-devsecops-pipeline-essential-open-source-security-tools</guid><category><![CDATA[DevSecOps]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Security]]></category><category><![CDATA[automation]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[ci-cd]]></category><dc:creator><![CDATA[Harshit Jain]]></dc:creator><pubDate>Wed, 22 Oct 2025 10:49:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761132095083/cd7caf32-377d-4544-95e0-49e96e805a0d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Security can no longer be an afterthought in modern software development. DevSecOps integrates security into every phase of the development pipeline, catching vulnerabilities early when they're cheapest to fix. The best part? You don't need expensive enterprise tools to get started.</p>
<p>In this article, we'll explore five powerful open-source security tools that cover your entire stack: GitHub CodeQL for code analysis, Dependabot for dependency management, Terrascan for Infrastructure as Code, Trivy for container scanning, and OWASP ZAP for dynamic testing.</p>
<h2 id="heading-1-github-codeql-static-application-security-testing-sast">1. GitHub CodeQL: Static Application Security Testing (SAST)</h2>
<h3 id="heading-what-it-does">What It Does</h3>
<p>CodeQL is GitHub's powerful semantic code analysis engine that treats code as data. Unlike traditional static analysis tools that rely on pattern matching, CodeQL allows you to write queries that can identify complex security vulnerabilities and coding patterns across your entire codebase.</p>
<h3 id="heading-key-features">Key Features</h3>
<ul>
<li><p><strong>Multi-language support</strong>: Works with JavaScript, TypeScript, Python, Java, C/C++, C#, Go, Ruby, and more</p>
</li>
<li><p><strong>Deep semantic analysis</strong>: Understands code structure and data flow, not just syntax</p>
</li>
<li><p><strong>Customizable queries</strong>: Write your own security queries or use the extensive community library</p>
</li>
<li><p><strong>CI/CD integration</strong>: Seamlessly integrates with GitHub Actions and other CI systems</p>
</li>
</ul>
<h3 id="heading-setting-up-codeql">Setting Up CodeQL</h3>
<p>GitHub makes it incredibly easy to enable CodeQL through the UI without writing any workflow files:</p>
<ol>
<li><p>Navigate to your repository on GitHub</p>
</li>
<li><p>Go to the <strong>Settings</strong> tab</p>
</li>
<li><p>Click on <strong>Code security and analysis</strong> in the left sidebar</p>
</li>
<li><p>Under "Code scanning", click <strong>Set up</strong> next to CodeQL analysis</p>
</li>
<li><p>Choose <strong>Default</strong> setup for automatic configuration</p>
</li>
<li><p>Select the languages you want to scan</p>
</li>
<li><p>Click <strong>Enable CodeQL</strong></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761129544958/bd7bd8ac-4956-4503-87f8-19d79951827e.png" alt class="image--center mx-auto" /></p>
<p>GitHub will automatically create scanning workflows and start analyzing your code on every push and pull request.</p>
<h3 id="heading-enable-secret-scanning">Enable Secret Scanning</h3>
<p>While you're in the Code security settings, enable secret scanning to prevent accidental exposure of credentials:</p>
<ol>
<li><p>Under "Secret scanning", enable:</p>
<ul>
<li><p><strong>Secret scanning</strong>: Detects secrets that have been committed to your repository</p>
</li>
<li><p><strong>Push protection</strong>: Blocks commits containing supported secrets before they reach your repository</p>
</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761129573589/40fec67a-2d19-4635-9972-3c97fb2aa52c.png" alt class="image--center mx-auto" /></p>
<p>Push protection is crucial—it prevents developers from accidentally committing API keys, tokens, passwords, and other credentials. When a push containing a secret is blocked, GitHub provides guidance on how to remove it safely.</p>
<h3 id="heading-reviewing-codeql-alerts">Reviewing CodeQL Alerts</h3>
<p>Once enabled, CodeQL will scan your repository and display any security vulnerabilities it finds:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761129656899/31c93de8-8973-437d-a0fd-44620a50c3d8.png" alt class="image--center mx-auto" /></p>
<p>Each alert provides:</p>
<ul>
<li><p>Detailed vulnerability description</p>
</li>
<li><p>Affected code location with highlighted lines</p>
</li>
<li><p>Severity level (Critical, High, Medium, Low)</p>
</li>
<li><p>Remediation guidance and fix suggestions</p>
</li>
<li><p>Ability to dismiss false positives with justification</p>
</li>
</ul>
<h3 id="heading-best-practices">Best Practices</h3>
<ul>
<li><p>Enable CodeQL on all repositories, especially those handling sensitive data</p>
</li>
<li><p>Review alerts regularly and triage them promptly—CodeQL provides detailed remediation guidance</p>
</li>
<li><p>Use the "Dismissed" feature wisely—document why alerts are false positives</p>
</li>
<li><p>Monitor the Security Overview dashboard for organization-wide security posture</p>
</li>
</ul>
<h2 id="heading-2-dependabot-automated-dependency-management">2. Dependabot: Automated Dependency Management</h2>
<h3 id="heading-what-it-does-1">What It Does</h3>
<p>Dependabot automatically monitors your project dependencies for known security vulnerabilities and outdated packages. It creates pull requests to update vulnerable dependencies, complete with changelog information and compatibility assessments.</p>
<h3 id="heading-key-features-1">Key Features</h3>
<ul>
<li><p><strong>Vulnerability alerts</strong>: Real-time notifications about security issues in your dependencies</p>
</li>
<li><p><strong>Automated updates</strong>: Creates PRs to update vulnerable packages</p>
</li>
<li><p><strong>Version compatibility</strong>: Understands semantic versioning and suggests appropriate updates</p>
</li>
<li><p><strong>Multi-ecosystem support</strong>: Works with npm, pip, Maven, NuGet, Composer, and more</p>
</li>
</ul>
<h3 id="heading-enabling-dependabot">Enabling Dependabot</h3>
<p>Like CodeQL, Dependabot can be enabled directly through GitHub's UI:</p>
<ol>
<li><p>Go to your repository <strong>Settings</strong></p>
</li>
<li><p>Navigate to <strong>Code security and analysis</strong></p>
</li>
<li><p>Under "Dependabot", enable the following:</p>
<ul>
<li><p><strong>Dependabot alerts</strong>: Get notified about vulnerable dependencies</p>
</li>
<li><p><strong>Dependabot security updates</strong>: Automatically create PRs to fix vulnerabilities</p>
</li>
<li><p><strong>Dependabot version updates</strong>: Keep dependencies up-to-date (optional)</p>
</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761129747050/c35f4ecf-35bc-4611-8b36-250ab42cc6a3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-managing-dependabot-alerts">Managing Dependabot Alerts</h3>
<p>Once enabled, Dependabot continuously monitors your dependencies:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761129896769/7b5d5cca-0a7a-46b9-9fd8-8fa344e50644.png" alt class="image--center mx-auto" /></p>
<p>The Dependabot alerts page shows:</p>
<ul>
<li><p>Vulnerable package name and affected versions</p>
</li>
<li><p>CVE details and severity scores</p>
</li>
<li><p>Which files are affected</p>
</li>
<li><p>Available patched versions</p>
</li>
<li><p>One-click option to create a security update PR</p>
</li>
</ul>
<h3 id="heading-security-impact">Security Impact</h3>
<p>Third-party dependencies account for a massive portion of modern application code—often 80% or more. A single vulnerable package can compromise your entire application. Dependabot helps you maintain a secure supply chain by ensuring you're always running patched versions of your dependencies.</p>
<h3 id="heading-best-practices-1">Best Practices</h3>
<ul>
<li><p>Enable Dependabot security updates immediately—they're free on GitHub</p>
</li>
<li><p>Set up automated testing to validate dependency updates before merging</p>
</li>
<li><p>Configure grouped updates for related packages to reduce PR noise</p>
</li>
<li><p>Review dependency changes carefully, especially major version bumps</p>
</li>
</ul>
<h2 id="heading-3-terrascan-infrastructure-as-code-security">3. Terrascan: Infrastructure as Code Security</h2>
<h3 id="heading-what-it-does-2">What It Does</h3>
<p>Terrascan is a static code analyzer for Infrastructure as Code that detects compliance and security violations before your cloud infrastructure is provisioned. It supports Terraform, Kubernetes, Helm, Dockerfiles, and more.</p>
<h3 id="heading-key-features-2">Key Features</h3>
<ul>
<li><p><strong>500+ built-in policies</strong>: Covers CIS Benchmarks, NIST, PCI-DSS, GDPR, and more</p>
</li>
<li><p><strong>Multi-cloud support</strong>: Works with AWS, Azure, GCP, and Kubernetes</p>
</li>
<li><p><strong>Custom policies</strong>: Write your own rules using Rego (Open Policy Agent)</p>
</li>
<li><p><strong>CI/CD integration</strong>: Easy integration with popular CI platforms</p>
</li>
</ul>
<h3 id="heading-installation-and-usage">Installation and Usage</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Install Terrascan</span>
curl -L <span class="hljs-string">"<span class="hljs-subst">$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest | grep -o -E <span class="hljs-string">"https://.+?_Linux_x86_64.tar.gz"</span>)</span>"</span> &gt; terrascan.tar.gz
tar -xf terrascan.tar.gz terrascan &amp;&amp; rm terrascan.tar.gz
sudo mv terrascan /usr/<span class="hljs-built_in">local</span>/bin

<span class="hljs-comment"># Scan Terraform code</span>
terrascan scan -t terraform -d /path/to/terraform

<span class="hljs-comment"># Scan with specific compliance framework</span>
terrascan scan -t terraform -d . -p aws -i cis

<span class="hljs-comment"># Generate SARIF output for GitHub</span>
terrascan scan -t terraform -d . -o sarif &gt; results.sarif
</code></pre>
<h3 id="heading-integration-example">Integration Example</h3>
<p>Add Terrascan to your CI pipeline:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># GitHub Actions</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terrascan</span> <span class="hljs-string">IaC</span> <span class="hljs-string">Scanner</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">tenable/terrascan-action@main</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">iac_type:</span> <span class="hljs-string">'terraform'</span>
    <span class="hljs-attr">iac_dir:</span> <span class="hljs-string">'infrastructure/'</span>
    <span class="hljs-attr">policy_type:</span> <span class="hljs-string">'aws'</span>
    <span class="hljs-attr">only_warn:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">sarif_upload:</span> <span class="hljs-literal">true</span>
</code></pre>
<h3 id="heading-why-iac-security-matters">Why IaC Security Matters</h3>
<p>Misconfigured cloud resources are among the most common causes of data breaches. Public S3 buckets, overly permissive IAM roles, and exposed databases have led to countless security incidents. Terrascan catches these issues before infrastructure is deployed, preventing costly mistakes.</p>
<h2 id="heading-4-trivy-comprehensive-container-security">4. Trivy: Comprehensive Container Security</h2>
<h3 id="heading-what-it-does-3">What It Does</h3>
<p>Trivy is an all-in-one vulnerability scanner for containers, filesystems, and Git repositories. It detects vulnerabilities in OS packages, application dependencies, IaC misconfigurations, and even secrets accidentally committed to code.</p>
<h3 id="heading-key-features-3">Key Features</h3>
<ul>
<li><p><strong>Multiple scan targets</strong>: Container images, filesystems, repositories, Kubernetes clusters</p>
</li>
<li><p><strong>Comprehensive vulnerability database</strong>: Covers OS packages and language-specific dependencies</p>
</li>
<li><p><strong>Fast and accurate</strong>: Uses multiple data sources for high-quality results</p>
</li>
<li><p><strong>Secret detection</strong>: Finds API keys, passwords, and tokens in your code</p>
</li>
<li><p><strong>Kubernetes integration</strong>: Scan running workloads for vulnerabilities and misconfigurations</p>
</li>
</ul>
<h3 id="heading-basic-usage">Basic Usage</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Install Trivy</span>
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
<span class="hljs-built_in">echo</span> <span class="hljs-string">"deb https://aquasecurity.github.io/trivy-repo/deb <span class="hljs-subst">$(lsb_release -sc)</span> main"</span> | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy

<span class="hljs-comment"># Scan a container image</span>
trivy image nginx:latest

<span class="hljs-comment"># Scan with severity filtering</span>
trivy image --severity HIGH,CRITICAL myapp:1.0.0

<span class="hljs-comment"># Scan a filesystem</span>
trivy fs /path/to/project

<span class="hljs-comment"># Scan for secrets</span>
trivy fs --scanners secret /path/to/repo

<span class="hljs-comment"># Generate reports</span>
trivy image --format json --output results.json myapp:latest
</code></pre>
<h3 id="heading-cicd-integration">CI/CD Integration</h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># GitHub Actions example</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Trivy</span> <span class="hljs-string">vulnerability</span> <span class="hljs-string">scanner</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">aquasecurity/trivy-action@master</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">image-ref:</span> <span class="hljs-string">'myregistry/myapp:$<span class="hljs-template-variable">{{ github.sha }}</span>'</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">'sarif'</span>
    <span class="hljs-attr">output:</span> <span class="hljs-string">'trivy-results.sarif'</span>
    <span class="hljs-attr">severity:</span> <span class="hljs-string">'CRITICAL,HIGH'</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Trivy</span> <span class="hljs-string">results</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Security</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">github/codeql-action/upload-sarif@v2</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">sarif_file:</span> <span class="hljs-string">'trivy-results.sarif'</span>
</code></pre>
<h3 id="heading-advanced-features">Advanced Features</h3>
<p>Trivy's versatility makes it invaluable for container security:</p>
<ul>
<li><p><strong>SBOM generation</strong>: Create Software Bill of Materials for compliance</p>
</li>
<li><p><strong>Kubernetes operator</strong>: Continuously scan running containers in your cluster</p>
</li>
<li><p><strong>Policy enforcement</strong>: Fail builds based on custom vulnerability thresholds</p>
</li>
<li><p><strong>Air-gapped environments</strong>: Can run offline with downloaded vulnerability databases</p>
</li>
</ul>
<h2 id="heading-5-owasp-zap-dynamic-application-security-testing">5. OWASP ZAP: Dynamic Application Security Testing</h2>
<h3 id="heading-what-it-does-4">What It Does</h3>
<p>While the previous tools focus on static analysis, OWASP ZAP (Zed Attack Proxy) performs dynamic application security testing by actively probing your running application for vulnerabilities. It's the world's most popular web application security scanner.</p>
<h3 id="heading-key-features-4">Key Features</h3>
<ul>
<li><p><strong>Active and passive scanning</strong>: Detects vulnerabilities through both traffic analysis and active testing</p>
</li>
<li><p><strong>OWASP Top 10 coverage</strong>: Identifies injection flaws, XSS, authentication issues, and more</p>
</li>
<li><p><strong>API testing</strong>: Supports REST, SOAP, GraphQL, and WebSocket testing</p>
</li>
<li><p><strong>Extensible</strong>: Hundreds of add-ons available for specialized testing</p>
</li>
<li><p><strong>Automation-friendly</strong>: Excellent CLI and API support for CI/CD</p>
</li>
</ul>
<h3 id="heading-getting-started">Getting Started</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Run ZAP in Docker</span>
docker pull zaproxy/zap-stable

<span class="hljs-comment"># Baseline scan (passive)</span>
docker run -t zaproxy/zap-stable zap-baseline.py -t https://yourapp.com

<span class="hljs-comment"># Full scan (active)</span>
docker run -t zaproxy/zap-stable zap-full-scan.py -t https://yourapp.com

<span class="hljs-comment"># API scan</span>
docker run -t zaproxy/zap-stable zap-api-scan.py -t https://api.yourapp.com/openapi.json -f openapi
</code></pre>
<h3 id="heading-cicd-integration-1">CI/CD Integration</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">zap_scan:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Scan</span> <span class="hljs-string">the</span> <span class="hljs-string">webapplication</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">ref:</span> <span class="hljs-string">master</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ZAP</span> <span class="hljs-string">Scan</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">zaproxy/action-full-scan@v0.12.0</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">docker_name:</span> <span class="hljs-string">'ghcr.io/zaproxy/zaproxy:stable'</span>
          <span class="hljs-attr">target:</span> <span class="hljs-string">'https://www.zaproxy.org/'</span>
          <span class="hljs-attr">rules_file_name:</span> <span class="hljs-string">'.zap/rules.tsv'</span>
          <span class="hljs-attr">cmd_options:</span> <span class="hljs-string">'-a'</span>
</code></pre>
<h3 id="heading-best-practices-2">Best Practices</h3>
<ul>
<li><p>Start with baseline scans in staging environments</p>
</li>
<li><p>Integrate ZAP early but configure it to not block deployments initially</p>
</li>
<li><p>Use authenticated scans to test protected functionality</p>
</li>
<li><p>Create custom scan policies that match your application's risk profile</p>
</li>
<li><p>Review false positives and tune configurations over time</p>
</li>
</ul>
<h2 id="heading-building-your-devsecops-pipeline">Building Your DevSecOps Pipeline</h2>
<p>Now that we've covered individual tools, let's look at how they work together in a complete DevSecOps pipeline:</p>
<h3 id="heading-development-phase">Development Phase</h3>
<ol>
<li><p><strong>CodeQL</strong> scans code on every commit and pull request</p>
</li>
<li><p><strong>Dependabot</strong> monitors dependencies and creates update PRs</p>
</li>
<li><p>Developers receive immediate feedback on security issues</p>
</li>
</ol>
<h3 id="heading-build-phase">Build Phase</h3>
<ol>
<li><p><strong>Terrascan</strong> validates Infrastructure as Code before deployment</p>
</li>
<li><p><strong>Trivy</strong> scans container images for vulnerabilities</p>
</li>
<li><p>Builds fail if critical vulnerabilities are detected</p>
</li>
</ol>
<h3 id="heading-testing-phase">Testing Phase</h3>
<ol>
<li><p><strong>OWASP ZAP</strong> performs dynamic security testing against staging environments</p>
</li>
<li><p>API security tests validate authentication and authorization</p>
</li>
<li><p>Security reports are generated and reviewed</p>
</li>
</ol>
<h3 id="heading-deployment-phase">Deployment Phase</h3>
<ol>
<li><p>Only passing builds with acceptable security posture are deployed</p>
</li>
<li><p>Runtime security monitoring begins (using tools like Falco or Sysdig)</p>
</li>
<li><p>Continuous scanning detects newly discovered vulnerabilities</p>
</li>
</ol>
<h2 id="heading-implementation-strategy">Implementation Strategy</h2>
<p>Don't try to implement everything at once. Here's a pragmatic rollout approach:</p>
<p><strong>Week 1-2</strong>: Enable CodeQL and Dependabot on your most critical repositories. These have minimal overhead and provide immediate value.</p>
<p><strong>Week 3-4</strong>: Integrate Trivy into your container build process. Start with warnings only, then gradually enforce policies.</p>
<p><strong>Week 5-6</strong>: Add Terrascan to your IaC repositories. Begin with audit mode to understand your current security posture.</p>
<p><strong>Week 7-8</strong>: Introduce OWASP ZAP for dynamic testing in staging environments. Initially run scans manually, then automate.</p>
<p><strong>Ongoing</strong>: Tune configurations, reduce false positives, train team members, and expand coverage.</p>
<h2 id="heading-measuring-success">Measuring Success</h2>
<p>Track these metrics to demonstrate the value of your DevSecOps implementation:</p>
<ul>
<li><p><strong>Mean Time to Remediation (MTTR)</strong>: How quickly vulnerabilities are fixed after discovery</p>
</li>
<li><p><strong>Vulnerability density</strong>: Number of vulnerabilities per thousand lines of code</p>
</li>
<li><p><strong>Security debt</strong>: Accumulation of known but unresolved security issues</p>
</li>
<li><p><strong>Shift-left effectiveness</strong>: Percentage of vulnerabilities caught before production</p>
</li>
<li><p><strong>False positive rate</strong>: Accuracy of security tools (aim for &lt;10%)</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building a secure software delivery pipeline doesn't require expensive enterprise tools. The open-source ecosystem provides powerful, production-ready solutions that cover every layer of your stack—from source code to running containers.</p>
<p>By integrating CodeQL, Dependabot, Terrascan, Trivy, and OWASP ZAP into your development workflow, you create multiple defensive layers that catch security issues early and often. The key is starting small, measuring results, and continuously improving your security posture.</p>
<p>Remember: DevSecOps is a journey, not a destination. Begin with one or two tools, demonstrate value to your organization, and gradually expand your security automation. Your future self—and your security team—will thank you.</p>
<h2 id="heading-additional-resources">Additional Resources</h2>
<ul>
<li><p><a target="_blank" href="https://securitylab.github.com/">GitHub Security Lab</a></p>
</li>
<li><p><a target="_blank" href="https://aquasecurity.github.io/trivy/">Trivy Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://runterrascan.io/docs/policies/">Terrascan Policies</a></p>
</li>
<li><p><a target="_blank" href="https://www.zaproxy.org/docs/">OWASP ZAP User Guide</a></p>
</li>
<li><p><a target="_blank" href="https://www.devsecops.org/">DevSecOps Manifesto</a></p>
</li>
</ul>
<p>Start securing your pipeline today—your code, your users, and your organization deserve it.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Helm Charts - From Confusion to Clarity]]></title><description><![CDATA[What is a Helm Chart? (Simple Answer)
A Helm chart is a ZIP file containing:

Kubernetes YAML templates (Deployment, Service, etc.)

A values.yaml file with default configuration

A way to customize those templates


Think of it like:

Template = A f...]]></description><link>https://tech.withharshit.com/understanding-helm-charts-from-confusion-to-clarity</link><guid isPermaLink="true">https://tech.withharshit.com/understanding-helm-charts-from-confusion-to-clarity</guid><category><![CDATA[helm working]]></category><category><![CDATA[devops in 2025]]></category><category><![CDATA[Helm]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[automation]]></category><category><![CDATA[deployment]]></category><dc:creator><![CDATA[Harshit Jain]]></dc:creator><pubDate>Fri, 03 Oct 2025 07:46:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761406502668/cf8d4068-e78a-47ce-9453-fb4c95641773.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-a-helm-chart-simple-answer">What is a Helm Chart? (Simple Answer)</h2>
<p><strong>A Helm chart is a ZIP file containing:</strong></p>
<ol>
<li><p>Kubernetes YAML templates (Deployment, Service, etc.)</p>
</li>
<li><p>A <code>values.yaml</code> file with default configuration</p>
</li>
<li><p>A way to customize those templates</p>
</li>
</ol>
<p><strong>Think of it like:</strong></p>
<ul>
<li><p><strong>Template</strong> = A form with blank fields</p>
</li>
<li><p><strong>values.yaml</strong> = The default values to fill in those blanks</p>
</li>
<li><p><strong>Your custom values</strong> = Overriding those defaults</p>
</li>
</ul>
<hr />
<h2 id="heading-the-confusion-templates-vs-values">The Confusion: Templates vs Values</h2>
<h3 id="heading-what-confuses-people">What Confuses People:</h3>
<pre><code class="lang-plaintext">❓ "There's a Deployment template in the chart..."
❓ "There's a values.yaml with settings..."
❓ "How do these connect?"
❓ "What can I actually change?"
❓ "Where do I put MY configuration?"
</code></pre>
<h3 id="heading-the-answer-visual">The Answer (Visual):</h3>
<pre><code class="lang-plaintext">HELM CHART (what the chart maintainers created)
│
├── templates/
│   ├── deployment.yaml          ← Template with {{ .Values.* }} placeholders
│   ├── service.yaml             ← Template with {{ .Values.* }} placeholders
│   └── configmap.yaml           ← Template with {{ .Values.* }} placeholders
│
└── values.yaml                  ← DEFAULT values that fill those placeholders
    image:
      repository: postgres
      tag: "15"
    persistence:
      enabled: true
      size: 8Gi

YOUR CUSTOM VALUES (what you provide)
│
└── my-values.yaml               ← YOUR values that OVERRIDE defaults
    image:
      tag: "16"                  ← Override: Use version 16 instead
    persistence:
      size: 20Gi                 ← Override: Use 20Gi instead of 8Gi

RESULT (what actually gets deployed)
│
└── Kubernetes YAML              ← Template filled with YOUR values
    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          containers:
          - image: postgres:16    ← From your my-values.yaml
            volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: postgres-pvc
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    spec:
      resources:
        requests:
          storage: 20Gi           ← From your my-values.yaml
</code></pre>
<hr />
<h2 id="heading-how-to-actually-use-a-helm-chart-step-by-step">How to Actually Use a Helm Chart (Step by Step)</h2>
<h3 id="heading-step-1-find-the-chart">Step 1: Find the Chart</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Search for a chart</span>
helm search hub postgresql

<span class="hljs-comment"># Add the repository</span>
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
</code></pre>
<h3 id="heading-step-2-look-at-what-you-can-configure">Step 2: Look at What You Can Configure</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># See ALL available options</span>
helm show values bitnami/postgresql

<span class="hljs-comment"># This outputs the ENTIRE values.yaml - save it!</span>
helm show values bitnami/postgresql &gt; default-values.yaml
</code></pre>
<p><strong>What you see (example):</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment">## @section Global parameters</span>
<span class="hljs-attr">global:</span>
  <span class="hljs-attr">imageRegistry:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">storageClass:</span> <span class="hljs-string">""</span>

<span class="hljs-comment">## @section Common parameters</span>
<span class="hljs-attr">architecture:</span> <span class="hljs-string">standalone</span>

<span class="hljs-comment">## @section PostgreSQL Authentication parameters</span>
<span class="hljs-attr">auth:</span>
  <span class="hljs-attr">enablePostgresUser:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">postgresPassword:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">database:</span> <span class="hljs-string">""</span>

<span class="hljs-comment">## @section PostgreSQL Primary parameters</span>
<span class="hljs-attr">primary:</span>
  <span class="hljs-attr">persistence:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">storageClass:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">size:</span> <span class="hljs-string">8Gi</span>

  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">limits:</span> {}
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">256Mi</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">250m</span>
</code></pre>
<h3 id="heading-step-3-understand-the-structure">Step 3: Understand the Structure</h3>
<p><strong>The values.yaml has a HIERARCHY:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Top level</span>
<span class="hljs-attr">auth:</span>                    <span class="hljs-string">←</span> <span class="hljs-string">Top-level</span> <span class="hljs-string">key</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">"myapp"</span>      <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'auth'</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">"secret"</span>     <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'auth'</span>
  <span class="hljs-attr">database:</span> <span class="hljs-string">"mydb"</span>       <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'auth'</span>

<span class="hljs-attr">primary:</span>                 <span class="hljs-string">←</span> <span class="hljs-string">Different</span> <span class="hljs-string">top-level</span> <span class="hljs-string">key</span>
  <span class="hljs-attr">persistence:</span>           <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'primary'</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>        <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'persistence'</span>
    <span class="hljs-attr">size:</span> <span class="hljs-string">20Gi</span>          <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'persistence'</span>
  <span class="hljs-attr">resources:</span>             <span class="hljs-string">←</span> <span class="hljs-string">Different</span> <span class="hljs-string">nested</span> <span class="hljs-string">key</span> <span class="hljs-string">under</span> <span class="hljs-string">'primary'</span>
    <span class="hljs-attr">limits:</span>              <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'resources'</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">2000m</span>         <span class="hljs-string">←</span> <span class="hljs-string">Nested</span> <span class="hljs-string">under</span> <span class="hljs-string">'limits'</span>
</code></pre>
<p><strong>This maps to template placeholders:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># In the chart's templates/deployment.yaml (you don't edit this):</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
    <span class="hljs-attr">image:</span> {{ <span class="hljs-string">.Values.image.repository</span> }}<span class="hljs-string">:{{</span> <span class="hljs-string">.Values.image.tag</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_USER</span>
      <span class="hljs-attr">value:</span> {{ <span class="hljs-string">.Values.auth.username</span> }}
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
      <span class="hljs-attr">value:</span> {{ <span class="hljs-string">.Values.auth.password</span> }}
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span>
      <span class="hljs-attr">value:</span> {{ <span class="hljs-string">.Values.auth.database</span> }}
</code></pre>
<p><strong>When you provide:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># your-values.yaml</span>
<span class="hljs-attr">auth:</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">"myapp"</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">"secret123"</span>
  <span class="hljs-attr">database:</span> <span class="hljs-string">"myappdb"</span>
</code></pre>
<p><strong>Helm renders it as:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:15</span>
    <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_USER</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">myapp</span>          <span class="hljs-string">←</span> <span class="hljs-string">YOUR</span> <span class="hljs-string">value</span> <span class="hljs-string">inserted</span> <span class="hljs-string">here</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">secret123</span>      <span class="hljs-string">←</span> <span class="hljs-string">YOUR</span> <span class="hljs-string">value</span> <span class="hljs-string">inserted</span> <span class="hljs-string">here</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">myappdb</span>        <span class="hljs-string">←</span> <span class="hljs-string">YOUR</span> <span class="hljs-string">value</span> <span class="hljs-string">inserted</span> <span class="hljs-string">here</span>
</code></pre>
<h3 id="heading-step-4-create-your-custom-values-file">Step 4: Create Your Custom Values File</h3>
<p><strong>DON'T copy the entire default values.yaml!</strong></p>
<p><strong>DO only specify what you want to CHANGE:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># my-postgres-values.yaml</span>

<span class="hljs-comment"># Only include what you're changing!</span>
<span class="hljs-attr">auth:</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">myapp</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">mySecurePassword123</span>
  <span class="hljs-attr">database:</span> <span class="hljs-string">myappdb</span>

<span class="hljs-attr">primary:</span>
  <span class="hljs-attr">persistence:</span>
    <span class="hljs-attr">size:</span> <span class="hljs-string">20Gi</span>

  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">limits:</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">2000m</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">4Gi</span>
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">500m</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">1Gi</span>
</code></pre>
<p><strong>Everything else uses the chart's defaults!</strong></p>
<h3 id="heading-step-5-install-the-chart">Step 5: Install the Chart</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Install with your custom values</span>
helm install my-postgres bitnami/postgresql -f my-postgres-values.yaml

<span class="hljs-comment"># Or install to specific namespace</span>
helm install my-postgres bitnami/postgresql \
  --namespace database \
  --create-namespace \
  -f my-postgres-values.yaml
</code></pre>
<p><strong>What happens:</strong></p>
<ol>
<li><p>Helm downloads the chart</p>
</li>
<li><p>Helm reads the chart's <code>values.yaml</code> (defaults)</p>
</li>
<li><p>Helm reads YOUR <code>my-postgres-values.yaml</code> (overrides)</p>
</li>
<li><p>Helm merges them (your values override defaults)</p>
</li>
<li><p>Helm fills the templates with the merged values</p>
</li>
<li><p>Helm applies the rendered YAML to Kubernetes</p>
</li>
</ol>
<hr />
<h2 id="heading-the-three-ways-to-provide-values">The Three Ways to Provide Values</h2>
<h3 id="heading-method-1-values-file-recommended">Method 1: Values File (Recommended)</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Create my-values.yaml</span>
cat &gt; my-values.yaml &lt;&lt;EOF
auth:
  username: myapp
  database: myappdb
EOF

<span class="hljs-comment"># Install with file</span>
helm install postgres bitnami/postgresql -f my-values.yaml
</code></pre>
<h3 id="heading-method-2-command-line-quick-testing">Method 2: Command Line (Quick Testing)</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Set individual values via --set</span>
helm install postgres bitnami/postgresql \
  --<span class="hljs-built_in">set</span> auth.username=myapp \
  --<span class="hljs-built_in">set</span> auth.database=myappdb \
  --<span class="hljs-built_in">set</span> primary.persistence.size=20Gi
</code></pre>
<h3 id="heading-method-3-multiple-values-files-advanced">Method 3: Multiple Values Files (Advanced)</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Combine multiple files</span>
helm install postgres bitnami/postgresql \
  -f base-values.yaml \
  -f production-overrides.yaml

<span class="hljs-comment"># Later values override earlier ones</span>
</code></pre>
<hr />
<h2 id="heading-common-confusion-points-solved">Common Confusion Points - SOLVED</h2>
<h3 id="heading-confusion-1-what-can-i-actually-configure">Confusion 1: "What can I actually configure?"</h3>
<p><strong>Answer:</strong> Look at the default values.yaml</p>
<pre><code class="lang-bash">helm show values bitnami/postgresql &gt; values.yaml
less values.yaml

<span class="hljs-comment"># Search for what you need</span>
grep -i <span class="hljs-string">"persistence"</span> values.yaml
grep -i <span class="hljs-string">"resources"</span> values.yaml
grep -i <span class="hljs-string">"password"</span> values.yaml
</code></pre>
<p><strong>If it's in values.yaml, you can configure it!</strong></p>
<h3 id="heading-confusion-2-how-do-i-know-the-correct-structure">Confusion 2: "How do I know the correct structure?"</h3>
<p><strong>Answer:</strong> Copy the structure EXACTLY from values.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># ❌ WRONG - flat structure</span>
<span class="hljs-attr">persistence.size:</span> <span class="hljs-string">20Gi</span>

<span class="hljs-comment"># ✅ CORRECT - nested structure (same as in values.yaml)</span>
<span class="hljs-attr">primary:</span>
  <span class="hljs-attr">persistence:</span>
    <span class="hljs-attr">size:</span> <span class="hljs-string">20Gi</span>
</code></pre>
<p><strong>Rule:</strong> Match the indentation/nesting from the default values.yaml</p>
<h3 id="heading-confusion-3-do-i-need-to-specify-everything">Confusion 3: "Do I need to specify everything?"</h3>
<p><strong>NO!</strong> Only specify what you want to change.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># ❌ Don't do this (500 lines)</span>
<span class="hljs-comment"># Copy entire values.yaml and modify a few lines</span>

<span class="hljs-comment"># ✅ Do this (10 lines)</span>
<span class="hljs-comment"># Only what you're changing</span>
<span class="hljs-attr">auth:</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">myapp</span>
  <span class="hljs-attr">database:</span> <span class="hljs-string">myappdb</span>
</code></pre>
<h3 id="heading-confusion-4-what-if-i-want-to-see-the-final-yaml">Confusion 4: "What if I want to see the final YAML?"</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Dry-run: See what will be created WITHOUT installing</span>
helm install postgres bitnami/postgresql \
  -f my-values.yaml \
  --dry-run \
  --debug

<span class="hljs-comment"># This shows you the FINAL rendered Kubernetes YAML</span>
</code></pre>
<h3 id="heading-confusion-5-how-do-i-know-if-my-values-are-valid">Confusion 5: "How do I know if my values are valid?"</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Template: Render locally without installing</span>
helm template postgres bitnami/postgresql -f my-values.yaml

<span class="hljs-comment"># If it works, your values are valid</span>
<span class="hljs-comment"># If it fails, you'll see the error</span>
</code></pre>
<hr />
<h2 id="heading-real-example-from-confusion-to-clarity">Real Example: From Confusion to Clarity</h2>
<h3 id="heading-scenario-deploy-postgresql">Scenario: Deploy PostgreSQL</h3>
<h4 id="heading-step-1-get-default-values">Step 1: Get Default Values</h4>
<pre><code class="lang-bash">helm repo add bitnami https://charts.bitnami.com/bitnami
helm show values bitnami/postgresql &gt; postgres-defaults.yaml
</code></pre>
<h4 id="heading-step-2-open-and-search">Step 2: Open and Search</h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Open in your editor</span>
code postgres-defaults.yaml

<span class="hljs-comment"># Search for key sections (Ctrl+F):</span>
<span class="hljs-comment"># - "auth"          ← Authentication</span>
<span class="hljs-comment"># - "persistence"   ← Storage</span>
<span class="hljs-comment"># - "resources"     ← CPU/Memory</span>
<span class="hljs-comment"># - "initdb"        ← Initial scripts</span>
</code></pre>
<h4 id="heading-step-3-find-what-you-need">Step 3: Find What You Need</h4>
<p><strong>In postgres-defaults.yaml you find:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment">## Authentication parameters</span>
<span class="hljs-attr">auth:</span>
  <span class="hljs-comment">## @param auth.enablePostgresUser Assign a password to the "postgres" admin user</span>
  <span class="hljs-attr">enablePostgresUser:</span> <span class="hljs-literal">true</span>
  <span class="hljs-comment">## @param auth.postgresPassword Password for the "postgres" admin user</span>
  <span class="hljs-attr">postgresPassword:</span> <span class="hljs-string">""</span>
  <span class="hljs-comment">## @param auth.username Name for a custom user to create</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">""</span>
  <span class="hljs-comment">## @param auth.password Password for the custom user to create</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">""</span>
  <span class="hljs-comment">## @param auth.database Name for a custom database to create</span>
  <span class="hljs-attr">database:</span> <span class="hljs-string">""</span>
</code></pre>
<p><strong>And:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">primary:</span>
  <span class="hljs-comment">## PostgreSQL Primary persistence configuration</span>
  <span class="hljs-attr">persistence:</span>
    <span class="hljs-comment">## @param primary.persistence.enabled Enable PostgreSQL Primary data persistence</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
    <span class="hljs-comment">## @param primary.persistence.storageClass PVC Storage Class</span>
    <span class="hljs-attr">storageClass:</span> <span class="hljs-string">""</span>
    <span class="hljs-comment">## @param primary.persistence.size PVC Storage Request</span>
    <span class="hljs-attr">size:</span> <span class="hljs-string">8Gi</span>
</code></pre>
<h4 id="heading-step-4-create-your-values-only-what-you-need">Step 4: Create YOUR Values (Only What You Need)</h4>
<pre><code class="lang-yaml"><span class="hljs-comment"># my-postgres-values.yaml</span>

<span class="hljs-comment"># Authentication (copied structure from defaults)</span>
<span class="hljs-attr">auth:</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">myapp</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">MySecurePass123</span>
  <span class="hljs-attr">database:</span> <span class="hljs-string">myappdb</span>

<span class="hljs-comment"># Storage (copied structure from defaults)</span>
<span class="hljs-attr">primary:</span>
  <span class="hljs-attr">persistence:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">storageClass:</span> <span class="hljs-string">local-path</span>  <span class="hljs-comment"># K3s storage class</span>
    <span class="hljs-attr">size:</span> <span class="hljs-string">20Gi</span>                <span class="hljs-comment"># Changed from default 8Gi</span>

<span class="hljs-comment"># Resources (copied structure from defaults)</span>
  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">limits:</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">2000m</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">4Gi</span>
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">500m</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">1Gi</span>
</code></pre>
<h4 id="heading-step-5-verify-before-installing">Step 5: Verify Before Installing</h4>
<pre><code class="lang-bash"><span class="hljs-comment"># See what will be created</span>
helm template test-postgres bitnami/postgresql \
  -f my-postgres-values.yaml \
  --namespace database

<span class="hljs-comment"># Look through the output - verify:</span>
<span class="hljs-comment"># - POSTGRES_USER = myapp ✓</span>
<span class="hljs-comment"># - POSTGRES_DB = myappdb ✓</span>
<span class="hljs-comment"># - PVC size = 20Gi ✓</span>
<span class="hljs-comment"># - CPU limits = 2000m ✓</span>
</code></pre>
<h4 id="heading-step-6-install">Step 6: Install</h4>
<pre><code class="lang-bash">helm install postgres bitnami/postgresql \
  -f my-postgres-values.yaml \
  --namespace database \
  --create-namespace
</code></pre>
<hr />
<h2 id="heading-the-mental-model">The Mental Model</h2>
<pre><code class="lang-plaintext">┌─────────────────────────────────────────┐
│         HELM CHART (Package)            │
│  ┌────────────────────────────────┐    │
│  │  templates/                     │    │
│  │    deployment.yaml              │    │
│  │    service.yaml                 │    │
│  │    configmap.yaml               │    │
│  │                                 │    │
│  │  These have placeholders:       │    │
│  │    {{ .Values.auth.username }}  │    │
│  │    {{ .Values.persistence.size }}│   │
│  └────────────────────────────────┘    │
│                                         │
│  ┌────────────────────────────────┐    │
│  │  values.yaml (DEFAULTS)         │    │
│  │    auth:                        │    │
│  │      username: ""               │    │
│  │    persistence:                 │    │
│  │      size: 8Gi                  │    │
│  └────────────────────────────────┘    │
└─────────────────────────────────────────┘
                    │
                    │ You provide
                    ↓
┌─────────────────────────────────────────┐
│   YOUR VALUES (my-values.yaml)          │
│     auth:                               │
│       username: myapp  ← Override       │
│     persistence:                        │
│       size: 20Gi       ← Override       │
└─────────────────────────────────────────┘
                    │
                    │ Helm merges
                    ↓
┌─────────────────────────────────────────┐
│       FINAL RENDERED YAML               │
│   (Actually deployed to Kubernetes)     │
│                                         │
│   Deployment with:                      │
│     - username: myapp                   │
│     - PVC size: 20Gi                    │
└─────────────────────────────────────────┘
</code></pre>
<hr />
<h2 id="heading-quick-reference-commands">Quick Reference Commands</h2>
<pre><code class="lang-bash"><span class="hljs-comment"># Search for charts</span>
helm search hub &lt;chart-name&gt;

<span class="hljs-comment"># Add repository</span>
helm repo add &lt;name&gt; &lt;url&gt;
helm repo update

<span class="hljs-comment"># View chart info</span>
helm show chart &lt;repo&gt;/&lt;chart&gt;

<span class="hljs-comment"># View README</span>
helm show readme &lt;repo&gt;/&lt;chart&gt;

<span class="hljs-comment"># View default values (IMPORTANT!)</span>
helm show values &lt;repo&gt;/&lt;chart&gt;
helm show values &lt;repo&gt;/&lt;chart&gt; &gt; values.yaml

<span class="hljs-comment"># Test your values without installing</span>
helm template &lt;name&gt; &lt;repo&gt;/&lt;chart&gt; -f my-values.yaml
helm install &lt;name&gt; &lt;repo&gt;/&lt;chart&gt; -f my-values.yaml --dry-run --debug

<span class="hljs-comment"># Install chart</span>
helm install &lt;name&gt; &lt;repo&gt;/&lt;chart&gt; -f my-values.yaml
helm install &lt;name&gt; &lt;repo&gt;/&lt;chart&gt; -f my-values.yaml -n &lt;namespace&gt; --create-namespace

<span class="hljs-comment"># Upgrade chart</span>
helm upgrade &lt;name&gt; &lt;repo&gt;/&lt;chart&gt; -f my-values.yaml

<span class="hljs-comment"># Check what's installed</span>
helm list
helm list -n &lt;namespace&gt;

<span class="hljs-comment"># Get values of installed release</span>
helm get values &lt;name&gt;
helm get values &lt;name&gt; -n &lt;namespace&gt;

<span class="hljs-comment"># Uninstall</span>
helm uninstall &lt;name&gt;
helm uninstall &lt;name&gt; -n &lt;namespace&gt;
</code></pre>
<hr />
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ol>
<li><p><strong>Chart = Templates + Default Values</strong></p>
</li>
<li><p><strong>You only configure values, NOT templates</strong></p>
</li>
<li><p><strong>Your values OVERRIDE defaults (you don't need to copy everything)</strong></p>
</li>
<li><p><strong>Match the EXACT structure from default values.yaml</strong></p>
</li>
<li><p><strong>Use</strong> <code>helm show values</code> to see what you can configure</p>
</li>
<li><p><strong>Use</strong> <code>helm template</code> or <code>--dry-run</code> to verify before installing</p>
</li>
<li><p><strong>Values hierarchy must match (indentation matters!)</strong></p>
</li>
</ol>
<hr />
<h2 id="heading-practice-exercise">Practice Exercise</h2>
<p>Try this right now:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># 1. Get the default values</span>
helm repo add bitnami https://charts.bitnami.com/bitnami
helm show values bitnami/nginx &gt; nginx-defaults.yaml

<span class="hljs-comment"># 2. Open and find these sections:</span>
<span class="hljs-comment"># - replicaCount</span>
<span class="hljs-comment"># - service.type</span>
<span class="hljs-comment"># - ingress.enabled</span>

<span class="hljs-comment"># 3. Create your own values file:</span>
cat &gt; my-nginx-values.yaml &lt;&lt;EOF
replicaCount: 3
service:
  <span class="hljs-built_in">type</span>: NodePort
EOF

<span class="hljs-comment"># 4. See what it will create:</span>
helm template <span class="hljs-built_in">test</span> bitnami/nginx -f my-nginx-values.yaml

<span class="hljs-comment"># 5. If it looks good, install:</span>
helm install mynginx bitnami/nginx -f my-nginx-values.yaml
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Terraform pipeline with popular open source tools]]></title><description><![CDATA[Overview:
Today we will look beyond terraform validate, plan and apply. While writing IaC, it is important to treat it like a code rather than just a bunch of tf files for spinning up the infrastructure.
Today in this article, I will focus on how to ...]]></description><link>https://tech.withharshit.com/terraform-pipeline-with-popular-open-source-tools</link><guid isPermaLink="true">https://tech.withharshit.com/terraform-pipeline-with-popular-open-source-tools</guid><category><![CDATA[Terraform]]></category><category><![CDATA[Azure]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[TFLint]]></category><category><![CDATA[trivy]]></category><category><![CDATA[Infrastructure as code]]></category><category><![CDATA[Pipeline]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[Harshit Jain]]></dc:creator><pubDate>Sun, 25 Aug 2024 17:31:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724606808857/52feb387-693e-41f7-a79e-522149a35eb0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview:</h2>
<p>Today we will look beyond terraform validate, plan and apply. While writing IaC, it is important to treat it like a code rather than just a bunch of tf files for spinning up the infrastructure.</p>
<p>Today in this article, I will focus on how to ensure clean IaC coding with required security measures by enforcing checks in the terraform pipeline.</p>
<h2 id="heading-scenarios">Scenarios:</h2>
<p>Now, there will be 3 actions we need to perform:</p>
<ol>
<li><p>Terraform Plan (<strong>Triggers at:</strong> PR level)</p>
</li>
<li><p>Terraform Apply (<strong>Triggers at:</strong> Code merge in default branch)</p>
</li>
<li><p>Terraform Destroy (<strong>Trigger:</strong> Manual trigger)</p>
</li>
</ol>
<p>In this article, we will focus on the Terraform plan part, which means checking the IaC at PR level (before applying the changes), because all the creativity will take place at this stage only. Rest both the stages (apply &amp; destroy) will be plan and simple.</p>
<h2 id="heading-cicd-tool-used">CICD Tool used:</h2>
<p>For this lab, I will be using <strong>Github actions</strong> as the CICD tool. If you are using any other tool like Azure DevOps or Jenkins, you can use the same steps there as well.</p>
<h2 id="heading-steps">Steps:</h2>
<ol>
<li><p>So the first thing in pipeline is specifying the trigger for the pipeline. Since this pipeline is for checking the code at PR level, the trigger will be set for PRs targeting the default branch.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">Plan</span>

 <span class="hljs-attr">on:</span>
   <span class="hljs-attr">pull_request:</span>
     <span class="hljs-attr">types:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">opened</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">synchronize</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">reopened</span>
     <span class="hljs-attr">branches:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-string">'main'</span>
</code></pre>
</li>
<li><p>Now starts the jobs, the first job will be for checking the code quality, possible errors, deprecated syntax, naming convention etc. For this, we will be using the open source tool <a target="_blank" href="https://github.com/terraform-linters/tflint">TFlint</a>.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">jobs:</span>
   <span class="hljs-attr">terraform-lint:</span>
     <span class="hljs-attr">name:</span> <span class="hljs-string">TFLint</span>
     <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
     <span class="hljs-attr">steps:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
         <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">terraform-linters/setup-tflint@v4</span>
         <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">TFLint</span>
         <span class="hljs-attr">with:</span>
           <span class="hljs-attr">tflint_version:</span> <span class="hljs-string">v0.53.0</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">TFLint</span>
         <span class="hljs-attr">run:</span> <span class="hljs-string">tflint</span>
         <span class="hljs-attr">working-directory:</span> <span class="hljs-string">infra</span>
</code></pre>
<p> Lets understand each step:</p>
<ul>
<li><p>First, we are checking out the code.</p>
</li>
<li><p>Then, we are using the Github action to setup/install the Tflint tool. You can also do this by using simple bash command.</p>
</li>
<li><p>Finally, we are running the tflint command in the directory that contains the IaC.</p>
</li>
</ul>
</li>
<li><p>Second job is for checking the code for security vulnerabilities, for this we will be using popular open source tool <a target="_blank" href="https://github.com/aquasecurity/trivy?tab=readme-ov-file">Trivy</a>.</p>
<pre><code class="lang-yaml">   <span class="hljs-attr">tf-security-scan:</span>
     <span class="hljs-attr">name:</span> <span class="hljs-string">trivy</span>
     <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
     <span class="hljs-attr">steps:</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
         <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">trivy</span>
         <span class="hljs-attr">run:</span> <span class="hljs-string">|
           sudo apt-get install wget apt-transport-https gnupg
           wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg &gt; /dev/null
           echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
           sudo apt-get update
           sudo apt-get install trivy
</span>
       <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">trivy</span>
         <span class="hljs-attr">run:</span> <span class="hljs-string">trivy</span> <span class="hljs-string">config</span> <span class="hljs-string">.</span>
         <span class="hljs-attr">working-directory:</span> <span class="hljs-string">infra</span>
</code></pre>
<p> Lets understand each step:</p>
<ul>
<li><p>Checkout code.</p>
</li>
<li><p>Install Trivy.</p>
</li>
<li><p>Run Trivy in the source directory.</p>
</li>
</ul>
</li>
<li><p>Once the security scan is successfully succeeded we will create a terraform plan, and post the plan in github PR comment. Commenting the terraform plan in PR will help the reviewer to overview the PR request quickly.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">terraform-plan:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">plan</span>
   <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
   <span class="hljs-attr">needs:</span> <span class="hljs-string">tf-security-scan</span>
   <span class="hljs-attr">permissions:</span>
     <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
     <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span> <span class="hljs-comment"># Required to post comments</span>

   <span class="hljs-attr">env:</span>
     <span class="hljs-attr">ARM_CLIENT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_CLIENT_ID</span> <span class="hljs-string">}}</span>
     <span class="hljs-attr">ARM_CLIENT_SECRET:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_CLIENT_SECRET</span> <span class="hljs-string">}}</span>
     <span class="hljs-attr">ARM_SUBSCRIPTION_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_SUBSCRIPTION_ID</span> <span class="hljs-string">}}</span>
     <span class="hljs-attr">ARM_TENANT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_TENANT_ID</span> <span class="hljs-string">}}</span>
     <span class="hljs-attr">TF_VAR_registry_password:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.TF_VAR_registry_password</span> <span class="hljs-string">}}</span>

   <span class="hljs-attr">steps:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
       <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Terraform</span>
       <span class="hljs-attr">uses:</span> <span class="hljs-string">hashicorp/setup-terraform@v3</span>
       <span class="hljs-attr">with:</span>
         <span class="hljs-attr">terraform_version:</span> <span class="hljs-number">1.9</span><span class="hljs-number">.4</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">Init</span>
       <span class="hljs-attr">run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">init</span>
       <span class="hljs-attr">working-directory:</span> <span class="hljs-string">infra</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">validate</span>
       <span class="hljs-attr">run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">validate</span>
       <span class="hljs-attr">working-directory:</span> <span class="hljs-string">infra</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Terraform</span> <span class="hljs-string">Plan</span>
       <span class="hljs-attr">run:</span> <span class="hljs-string">terraform</span> <span class="hljs-string">plan</span> <span class="hljs-string">-var-file=dev.tfvars</span> <span class="hljs-string">-out=.planfile</span>
       <span class="hljs-attr">working-directory:</span> <span class="hljs-string">infra</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Post</span> <span class="hljs-string">terraform</span> <span class="hljs-string">plan</span> <span class="hljs-string">comment</span>
       <span class="hljs-attr">uses:</span> <span class="hljs-string">borchero/terraform-plan-comment@v2</span>
       <span class="hljs-attr">with:</span>
         <span class="hljs-attr">token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.token</span> <span class="hljs-string">}}</span>
         <span class="hljs-attr">working-directory:</span> <span class="hljs-string">infra</span>
         <span class="hljs-attr">planfile:</span> <span class="hljs-string">.planfile</span>
</code></pre>
<p> Lets understand the steps:</p>
<ul>
<li><p>In Github actions, we get a <code>github.token</code> which has a lifecycle of that pipeline run. That means, we don't have to provide any PAT token to the pipeline for authentication. To read more about <code>github.token</code> visit <a target="_blank" href="https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication">here</a>.</p>
</li>
<li><p>So first, we will provide a permission block, this block is for providing permissions to our github token which will be used for commenting on the PR.</p>
</li>
<li><p>Next, we will define all the env variables for terraform plan, basically authenticating to our cloud provider.</p>
</li>
<li><p>Next, checkout the code.</p>
</li>
<li><p>Terraform install using github action (can be done by using bash commands).</p>
</li>
<li><p>Terraform initialise for downloading the modules.</p>
</li>
<li><p>Terraform validate to check the syntax.</p>
</li>
<li><p>Terraform plan by mentioning the var file and outputting the output in .planfile</p>
</li>
<li><p>Now, we will use the github action <code>borchero/terraform-plan-comment@v2</code> for commenting terraform plan output, it will take .planfile as an input and will post a well formatted comment on the PR.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724575573803/2362f9a0-a398-4aaf-bca8-9c3a08920a7b.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p>At last, we will compare the change in infra between default branch and PR and will comment the change in cost on PR using open source tool <a target="_blank" href="https://github.com/infracost/infracost">Infracost</a>. This tool is for cost estimation of our infra change.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">Infracost:</span>
   <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
   <span class="hljs-attr">needs:</span> <span class="hljs-string">terraform-plan</span>
   <span class="hljs-attr">permissions:</span>
     <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
     <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>

   <span class="hljs-attr">steps:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Infracost</span>
       <span class="hljs-attr">uses:</span> <span class="hljs-string">infracost/actions/setup@v3</span>
       <span class="hljs-attr">with:</span>
         <span class="hljs-attr">api-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.INFRACOST_API_KEY</span> <span class="hljs-string">}}</span>

     <span class="hljs-comment"># Checkout the base branch of the pull request (e.g. main/master).</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">base</span> <span class="hljs-string">branch</span>
       <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
       <span class="hljs-attr">with:</span>
         <span class="hljs-attr">ref:</span> <span class="hljs-string">'$<span class="hljs-template-variable">{{ github.event.pull_request.base.ref }}</span>'</span>

     <span class="hljs-comment"># Generate Infracost JSON file as the baseline.</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">Infracost</span> <span class="hljs-string">cost</span> <span class="hljs-string">estimate</span> <span class="hljs-string">baseline</span>
       <span class="hljs-attr">run:</span> <span class="hljs-string">|
         infracost breakdown --path=infra \
           --format=json \
           --out-file=/tmp/infracost-base.json \
           --terraform-var-file dev.tfvars
</span>
     <span class="hljs-comment"># Checkout the current PR branch so we can create a diff.</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">PR</span> <span class="hljs-string">branch</span>
       <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

     <span class="hljs-comment"># Generate an Infracost diff and save it to a JSON file.</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">Infracost</span> <span class="hljs-string">diff</span>
       <span class="hljs-attr">run:</span> <span class="hljs-string">|
         infracost diff --path=infra \
           --format=json \
           --compare-to=/tmp/infracost-base.json \
           --out-file=/tmp/infracost.json \
           --terraform-var-file dev.tfvars
</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Post</span> <span class="hljs-string">Infracost</span> <span class="hljs-string">comment</span>
       <span class="hljs-attr">run:</span> <span class="hljs-string">|
         infracost comment github --path=/tmp/infracost.json \
           --repo=$GITHUB_REPOSITORY \
           --github-token=${{ github.token }} \
           --pull-request=${{ github.event.pull_request.number }} \
           --behavior=update</span>
</code></pre>
<p> Lets understand each step in detail:</p>
<ul>
<li><p>Setup the infracost using the Infracost API key, you can get this by running the command <code>infracost auth login &amp;&amp; infracost configure get api_key</code>.</p>
</li>
<li><p>Checkout base branch, which is target branch for the pull request.</p>
</li>
<li><p>Generate a cost estimate report for the default branch.</p>
</li>
<li><p>Now checkout to the PR branch.</p>
</li>
<li><p>Now generate a cost difference by comparing current branch with default branch.</p>
</li>
<li><p>Post the report in the comment of the PR.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724604798745/685e0a3a-1944-42e8-8a2b-1c4908a36659.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
]]></content:encoded></item></channel></rss>