<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Gokul Bansal</title>
    <description>The latest articles on Forem by Gokul Bansal (@bansalgokul).</description>
    <link>https://forem.com/bansalgokul</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1133950%2F640cfe8b-9f53-48d7-86f2-5de3246a7bc9.jpeg</url>
      <title>Forem: Gokul Bansal</title>
      <link>https://forem.com/bansalgokul</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bansalgokul"/>
    <language>en</language>
    <item>
      <title>Migrating from Bitnami to Our Own PostgreSQL Charts at Harness</title>
      <dc:creator>Gokul Bansal</dc:creator>
      <pubDate>Sun, 25 Jan 2026 11:11:58 +0000</pubDate>
      <link>https://forem.com/bansalgokul/migrating-from-bitnami-to-our-own-postgresql-charts-at-harness-5dm</link>
      <guid>https://forem.com/bansalgokul/migrating-from-bitnami-to-our-own-postgresql-charts-at-harness-5dm</guid>
      <description>&lt;p&gt;When Bitnami announced they would stop providing updates to their open-source charts and container images, many enterprises faced a critical decision. At Harness, we provide a Self-Managed Enterprise Edition (SMP) as an on-premises solution for enterprises with strict security and regulatory requirements. We couldn't afford breaking changes—every customer has different overrides, and they all needed to keep working.&lt;/p&gt;

&lt;p&gt;This is how we migrated from Bitnami's PostgreSQL chart to our own, using official PostgreSQL images (with RapidFort support) &lt;strong&gt;without breaking a single customer deployment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Helm Charts available here - &lt;a href="https://github.com/bansalgokul/open-helm-charts" rel="noopener noreferrer"&gt;https://github.com/bansalgokul/open-helm-charts&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Enterprise Migration Without Breaking Changes
&lt;/h2&gt;

&lt;p&gt;Creating a simple PostgreSQL Helm chart? That's about an hour of work. But migrating an enterprise-grade deployment off Bitnami while preserving all existing functionality? That's a completely different challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constraints:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No breaking changes&lt;/strong&gt;: Every customer's existing Helm values must continue working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No custom Docker images&lt;/strong&gt;: We wanted to use official PostgreSQL images to avoid maintenance overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full feature parity&lt;/strong&gt;: All Bitnami-supported features (TLS, LDAP, replication, audit) must work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise-grade&lt;/strong&gt;: Backup jobs, proper probes, configuration management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bitnami's approach relies heavily on custom Docker images with scripts that parse environment variables and update configuration files. We needed to achieve the same result using standard PostgreSQL images and Helm templates alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Migrated
&lt;/h2&gt;

&lt;p&gt;When Bitnami announced their transition to Bitnami Secure Images, enterprises had limited options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Buy Bitnami Secure&lt;/strong&gt;: Continue operating without issues, but at significant cost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build your own charts&lt;/strong&gt;: Requires upfront investment, but provides independence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We chose option 2. The benefits were clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Independence&lt;/strong&gt;: No dependency on Bitnami's roadmap or pricing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Support for both official PostgreSQL images and RapidFort's hardened variants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardization&lt;/strong&gt;: Align with upstream PostgreSQL, not vendor-specific implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability&lt;/strong&gt;: Eliminate vendor lock-in and custom image maintenance overhead&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The POC: Proving It Was Possible
&lt;/h2&gt;

&lt;p&gt;I started with a proof-of-concept. The approach seemed feasible:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone Bitnami's charts&lt;/strong&gt; as a starting point&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend ConfigMaps with helper functions&lt;/strong&gt; to parse old environment variables directly in Helm templates (no Docker image scripts needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create backup job templates&lt;/strong&gt; in the chart for backup functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace Bitnami probes&lt;/strong&gt; with &lt;code&gt;pg_isready&lt;/code&gt; instead of file-based checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move configuration to ConfigMaps&lt;/strong&gt; instead of environment-variable parsing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The POC worked. But then I hit &lt;strong&gt;the blocker&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Blocker: Configuration File Mounting
&lt;/h2&gt;

&lt;p&gt;Here's where things got interesting. Bitnami stores configuration files in a separate directory using volume mounts, then uses scripts in their Docker images to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy config files to PostgreSQL's required directory&lt;/li&gt;
&lt;li&gt;Parse environment variables&lt;/li&gt;
&lt;li&gt;Generate configuration lines&lt;/li&gt;
&lt;li&gt;Append them to &lt;code&gt;postgresql.conf&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For our use case, I needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create ConfigMaps with the configuration&lt;/li&gt;
&lt;li&gt;Mount them directly to PostgreSQL's data directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;During upgrades&lt;/strong&gt;: This works perfectly. The database is already initialized, so PostgreSQL's &lt;code&gt;initdb&lt;/code&gt; scripts don't run. Config files mount and work as expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;During fresh installs&lt;/strong&gt;: PostgreSQL runs &lt;code&gt;initdb&lt;/code&gt;, which &lt;strong&gt;requires the data directory to be empty&lt;/strong&gt;. But since we're mounting config files, the directory isn't empty, and the install fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This couldn't be solved with Helm init containers or init scripts because we needed the config files to be absent during &lt;code&gt;initdb&lt;/code&gt;, then injected afterward. This seemed to require Docker image customization—exactly what we wanted to avoid.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Breakthrough: PostgreSQL's Hidden Configuration Option
&lt;/h2&gt;

&lt;p&gt;After hitting the blocker, I took a break. The next day, I dove deep into PostgreSQL's documentation, looking for any way to separate the configuration directory from the data directory.&lt;/p&gt;

&lt;p&gt;I found that PostgreSQL allows you to specify paths for different components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;postgresql.conf&lt;/code&gt; location&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pg_hba.conf&lt;/code&gt; location&lt;/li&gt;
&lt;li&gt;Data directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My first attempt: Put &lt;code&gt;postgresql.conf&lt;/code&gt; in a separate &lt;code&gt;conf&lt;/code&gt; directory and reference it from the data directory. But PostgreSQL started treating the &lt;code&gt;conf&lt;/code&gt; directory as the main directory, which broke everything.&lt;/p&gt;

&lt;p&gt;Then I found it: &lt;strong&gt;PostgreSQL supports specifying the config file path via command-line arguments&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By passing &lt;code&gt;-c config_file=/path/to/postgresql.conf&lt;/code&gt; in the container's command, PostgreSQL will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read &lt;code&gt;postgresql.conf&lt;/code&gt; from the specified path (our ConfigMap mount)&lt;/li&gt;
&lt;li&gt;Keep the data directory at its standard location&lt;/li&gt;
&lt;li&gt;Allow &lt;code&gt;initdb&lt;/code&gt; to run successfully (since the data directory itself is empty)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the solution. We could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mount config files to a separate &lt;code&gt;conf&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Pass the config file path via command-line arguments&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;initdb&lt;/code&gt; run normally (data directory is empty)&lt;/li&gt;
&lt;li&gt;PostgreSQL reads config from the mounted ConfigMap&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; PostgreSQL's command-line argument support for &lt;code&gt;config_file&lt;/code&gt; allows us to decouple configuration from the data directory, solving the fresh install problem without any Docker image customization.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation Details: What We Built
&lt;/h2&gt;

&lt;p&gt;With the configuration path solution in hand, here's what we implemented:&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ConfigMaps for all configuration&lt;/strong&gt;: &lt;code&gt;postgresql.conf&lt;/code&gt; and &lt;code&gt;pg_hba.conf&lt;/code&gt; generated from Helm templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command-line config file path&lt;/strong&gt;: &lt;code&gt;postgres -c config_file=/conf/postgresql.conf&lt;/code&gt; allows mounting configs separately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helper functions in templates&lt;/strong&gt;: Parse old Bitnami environment variables (&lt;code&gt;POSTGRESQL_*&lt;/code&gt;) and convert to ConfigMap entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Docker image scripts&lt;/strong&gt;: Everything handled in Helm templates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Path Compatibility
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configurable paths&lt;/strong&gt;: Support both Bitnami (&lt;code&gt;/bitnami/postgresql/data&lt;/code&gt;) and standard (&lt;code&gt;/var/lib/postgresql/data&lt;/code&gt;) paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration support&lt;/strong&gt;: Detect existing Bitnami data and migrate to new paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets and certs&lt;/strong&gt;: Configurable mount paths for flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Feature Parity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backup jobs&lt;/strong&gt;: Template-based backup CronJobs using &lt;code&gt;pg_dump&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness probes&lt;/strong&gt;: &lt;code&gt;pg_isready&lt;/code&gt; instead of file-based checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password updates&lt;/strong&gt;: Direct &lt;code&gt;psql&lt;/code&gt; commands with &lt;code&gt;PGPASSWORD&lt;/code&gt; instead of Bitnami scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLS, LDAP, Replication&lt;/strong&gt;: All configured via ConfigMaps, not environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dual Image Support
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Official PostgreSQL images&lt;/strong&gt;: Standard upstream images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RapidFort hardened images&lt;/strong&gt;: Enhanced security with same functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock-in&lt;/strong&gt;: Easy to switch between image providers&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Results: Success Without Breaking Changes
&lt;/h2&gt;

&lt;p&gt;The migration was successful. From the user's perspective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Existing Helm values continue working&lt;/strong&gt;: All Bitnami-style overrides are supported&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;No breaking changes&lt;/strong&gt;: Upgrades don't require rewriting values&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Data preserved&lt;/strong&gt;: All databases and data intact during migration&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Feature complete&lt;/strong&gt;: TLS, LDAP, replication, audit all functional&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Image flexibility&lt;/strong&gt;: Support for both official and RapidFort images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We achieved independence from Bitnami while maintaining full backward compatibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open Source and Future Plans
&lt;/h2&gt;

&lt;p&gt;I am planning to open-source these charts in a GitHub repository. The approach developed can be applied to other databases as well. We have plans to migrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB&lt;/strong&gt;: Similar challenges with configuration and initialization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ClickHouse&lt;/strong&gt;: Another database in our stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional databases&lt;/strong&gt;: As needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The major benefit is that these charts support both official images and RapidFort images for additional security, providing true independence from any single vendor.&lt;/p&gt;

&lt;p&gt;I'd love for others to contribute and extend this approach to whatever images they need. The pattern is proven: use Helm templates to replace Docker image scripts, leverage PostgreSQL's native configuration options, and maintain backward compatibility.&lt;/p&gt;

&lt;p&gt;Helm Charts available here - &lt;a href="https://github.com/bansalgokul/open-helm-charts" rel="noopener noreferrer"&gt;https://github.com/bansalgokul/open-helm-charts&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Image changes are never "just image changes."&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base OS changes (Debian → Alpine) introduce compatibility concerns&lt;/li&gt;
&lt;li&gt;Extension availability varies by image base&lt;/li&gt;
&lt;li&gt;Default configurations differ between image families&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backward compatibility needs explicit design.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication methods must be preserved&lt;/li&gt;
&lt;li&gt;Configuration defaults can silently change&lt;/li&gt;
&lt;li&gt;Template defaults need security review&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Standard PostgreSQL configs are more portable (even if more verbose).&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ConfigMaps are more explicit than env vars&lt;/li&gt;
&lt;li&gt;Easier to audit and version control&lt;/li&gt;
&lt;li&gt;Less hidden behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data path mismatches are the #1 risk in Postgres migrations.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;But authentication and extension availability are close seconds&lt;/li&gt;
&lt;li&gt;Always verify both data integrity AND configuration integrity&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Comprehensive snapshots are essential.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-upgrade: Document everything (databases, tables, rows, configs)&lt;/li&gt;
&lt;li&gt;Post-upgrade: Compare systematically&lt;/li&gt;
&lt;li&gt;Automation makes this practical&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security defaults matter.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trust&lt;/code&gt; vs &lt;code&gt;md5&lt;/code&gt; authentication is a critical security difference&lt;/li&gt;
&lt;li&gt;Default templates need security review&lt;/li&gt;
&lt;li&gt;LDAP templates should also use secure defaults&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deep documentation dives pay off.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL's &lt;code&gt;-c config_file&lt;/code&gt; option was the key to solving our blocker&lt;/li&gt;
&lt;li&gt;Sometimes the solution is in the docs, not in custom code&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>postgres</category>
      <category>bitnami</category>
      <category>resources</category>
    </item>
    <item>
      <title>🕒 How Long Ago? A TimeAgo Component in React ⏳</title>
      <dc:creator>Gokul Bansal</dc:creator>
      <pubDate>Sun, 06 Aug 2023 09:32:33 +0000</pubDate>
      <link>https://forem.com/bansalgokul/how-long-ago-a-timeago-component-in-react-586i</link>
      <guid>https://forem.com/bansalgokul/how-long-ago-a-timeago-component-in-react-586i</guid>
      <description>&lt;p&gt;📅 Timestamp: 2023-08-06T12:30:00&lt;/p&gt;

&lt;p&gt;Ever wondered how to display the time elapsed since a specific event? 🤔 With the TimeAgo component in React, you can do just that! 🚀&lt;/p&gt;

&lt;p&gt;Simply pass the timestamp as a prop, and the TimeAgo component will automatically calculate the time elapsed since that moment. 💡&lt;/p&gt;

&lt;p&gt;🕑 Examples:&lt;br&gt;
timestamp="2023-08-06T12:30:00" ➡️ "Less than a minute ago"&lt;br&gt;
timestamp="2023-08-05T15:45:00" ➡️ "about 21 hours ago"&lt;br&gt;
timestamp="2023-08-04T10:00:00" ➡️ "2 days ago"&lt;/p&gt;

&lt;p&gt;Now you can keep your users informed about the freshness of the content or show how long ago a particular event occurred. ⏰&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { parseISO, formatDistanceToNow } from "date-fns";

const TimeAgo = ({ timestamp }) =&amp;gt; {
    let timeAgo = "";

    if (timestamp) {
        const date = parseISO(timestamp);
        const timePeriod = formatDistanceToNow(date);
        timeAgo = `${timePeriod} ago`;
    }

    return (
        &amp;lt;span&amp;gt;
            &amp;amp;nbsp; &amp;lt;i&amp;gt;{timeAgo}&amp;lt;/i&amp;gt;
        &amp;lt;/span&amp;gt;
    );
};

export default TimeAgo;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚀 Give it a try and boost your app's interactivity with TimeAgo! Happy coding! 😄&lt;/p&gt;

&lt;p&gt;Code - &lt;a href="https://github.com/bansalgokul/React-Components" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
Date-fns (npm) - &lt;a href="https://date-fns.org/v2.30.0/docs/formatDistanceToNow" rel="noopener noreferrer"&gt;Library&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>datefns</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
