<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhammad Ahmad</title>
    <description>The latest articles on DEV Community by Muhammad Ahmad (@ahmadraza100).</description>
    <link>https://dev.to/ahmadraza100</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%2F1150506%2Fbb00ec23-30e1-4d79-97ab-4d20f4922645.jpeg</url>
      <title>DEV Community: Muhammad Ahmad</title>
      <link>https://dev.to/ahmadraza100</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmadraza100"/>
    <language>en</language>
    <item>
      <title>Every tutorial tells you to add .env to .gitignore. That's not enough.</title>
      <dc:creator>Muhammad Ahmad</dc:creator>
      <pubDate>Sat, 30 May 2026 03:22:32 +0000</pubDate>
      <link>https://dev.to/ahmadraza100/every-tutorial-tells-you-to-add-env-to-gitignore-thats-not-enough-heres-what-i-built-43c8</link>
      <guid>https://dev.to/ahmadraza100/every-tutorial-tells-you-to-add-env-to-gitignore-thats-not-enough-heres-what-i-built-43c8</guid>
      <description>&lt;p&gt;Here's something nobody talks about.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; doesn't encrypt your secrets. It just hides them from git.&lt;/p&gt;

&lt;p&gt;They're still sitting on your laptop as plaintext. Every tool you install can read them. Every script that runs can read them. One accidental commit and your database password is public on GitHub forever.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;dotlock&lt;/strong&gt; — an encrypted &lt;code&gt;.env&lt;/code&gt; vault with a terminal UI, written in Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jbkasvnk3lcxymmuizt.png" alt=" " width="800" height="610"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before dotlock&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://localhost/myapp   &lt;span class="c"&gt;# plaintext, readable by anything&lt;/span&gt;
&lt;span class="nv"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk_live_abc123                 &lt;span class="c"&gt;# one grep away from anyone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After dotlock&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .dotlock file on disk — looks like this:&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;encrypted binary — unreadable without your private key]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How it works under 10 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
dotlock &lt;span class="nb"&gt;set &lt;/span&gt;DATABASE_URL &lt;span class="c"&gt;# prompts for value, input is masked&lt;/span&gt;
dotloc               &lt;span class="c"&gt;# opens the terminal UI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secrets are encrypted with &lt;a href="https://age-encryption.org" rel="noopener noreferrer"&gt;age&lt;/a&gt; — X25519 key agreement and ChaCha20-Poly1305 authenticated encryption. The same primitives serious security engineers use. No master password. No cloud. No telemetry. 100% offline.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jbkasvnk3lcxymmuizt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jbkasvnk3lcxymmuizt.png" alt=" " width="800" height="610"&gt;&lt;/a&gt;&lt;br&gt;
Two panels — profiles on the left, secrets on the right. Values are masked by default. Press &lt;code&gt;v&lt;/code&gt; to reveal for 30 seconds, then it hides itself automatically.&lt;/p&gt;

&lt;p&gt;Switch between &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, and &lt;code&gt;prod&lt;/code&gt; profiles. Run a diff before deploying to catch missing variables before they break your app.&lt;/p&gt;


&lt;h2&gt;
  
  
  The interesting technical bit
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't the encryption — &lt;a href="https://age-encryption.org" rel="noopener noreferrer"&gt;filippo.io/age&lt;/a&gt; makes that straightforward.&lt;/p&gt;

&lt;p&gt;It was the TUI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/charmbracelet/bubbletea" rel="noopener noreferrer"&gt;BubbleTea&lt;/a&gt; uses the Elm architecture — Model, Update, View. Everything is a message. A keypress is a message. A timer firing is a message. Your &lt;code&gt;Update&lt;/code&gt; function receives messages and returns a new model.&lt;/p&gt;

&lt;p&gt;The 30-second auto-hide on secret reveal works like this — no &lt;code&gt;time.Sleep&lt;/code&gt;, no goroutines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;secretReveal&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="n"&gt;expire&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;  &lt;span class="c"&gt;// Now() + 30 seconds&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On every render, check if &lt;code&gt;time.Now()&lt;/code&gt; is past the expiry. If it is, zero the bytes and clear the display. Simple once you understand the model but it took me longer than I expected to get right.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/ahmadraza100/dotlock@latest
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
dotlock init
dotlock ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or download a binary from the &lt;a href="https://github.com/ahmadraza100/dotlock/releases/latest" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; — Mac, Linux, and Windows all supported.&lt;/p&gt;

&lt;p&gt;Full source, architecture docs, and security model on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/ahmadraza100/dotlock" rel="noopener noreferrer"&gt;github.com/ahmadraza100/dotlock&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;What do you currently use for managing local secrets? Curious what others are doing — drop it in the comments.&lt;/p&gt;

</description>
      <category>go</category>
      <category>security</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
