<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://dotenvx.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://dotenvx.com/" rel="alternate" type="text/html" /><updated>2026-04-18T05:19:40+00:00</updated><id>https://dotenvx.com/feed.xml</id><title type="html">Dotenvx</title><subtitle>Secrets for agents</subtitle><author><name>dotenv</name></author><entry><title type="html">Harden .env.local: dotenvx + OS Keychain</title><link href="https://dotenvx.com/blog/2026/04/02/dotenvx-keychain.html" rel="alternate" type="text/html" title="Harden .env.local: dotenvx + OS Keychain" /><published>2026-04-02T00:00:00+00:00</published><updated>2026-04-02T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2026/04/02/dotenvx-keychain</id><content type="html" xml:base="https://dotenvx.com/blog/2026/04/02/dotenvx-keychain.html"><![CDATA[<p><em>Guest Post by <a href="https://ustunozgur.com">Ustun Ozgur</a></em></p>

<blockquote>
  <p><em>This post originally appeared on <a href="https://dev.to/ustun/a-small-hardening-trick-for-envlocal-dotenvx-os-keychain-2533">dev.to</a>.</em></p>
</blockquote>

<p>Most teams keep local secrets in <code>.env.local</code> and add that file to <code>.gitignore</code>.
That is the bare minimum, and it does not address a more pressing risk: supply
chain attacks and compromised local tooling that read <code>.env</code> files as soon as
they get repo access.</p>

<p>Once a malicious dependency, postinstall script, editor extension, MCP server,
AI coding tool, or other local helper can inspect your workspace, plain-text
<code>.env.local</code> files become low-effort, high-value targets.</p>

<p>I wanted a low-friction way to reduce that blast radius without forcing the whole
team onto a heavyweight secrets manager for day-to-day local development.</p>

<p>This is the pattern I landed on:</p>

<ul>
  <li>keep non-secret local config in <code>.env.local</code></li>
  <li>move actual secrets into <code>.env.local.secrets</code></li>
  <li>encrypt <code>.env.local.secrets</code> with <code>dotenvx</code></li>
  <li>move the decryption key out of disk and into macOS Keychain</li>
  <li>load <code>.env.local</code> first, then only decrypt secrets when an explicit opt-in
flag says to</li>
</ul>

<p>Important distinction: I am <strong>not</strong> using <code>dotenvx</code> the way it is often
marketed, where encrypted env files are committed to the repo and shared that
way. This setup is local-only. The encrypted file and <code>.env.keys</code> both stay
uncommitted, and I prefer it that way. Committing encrypted env files is useful
when you want team-wide encrypted config distribution, but that was not my goal.
I wanted to reduce plaintext secrets on developer machines and raise the cost of
tools that slurp local env files, while keeping the workflow simple enough that
teammates actually use it.</p>

<h2 id="the-setup">The setup</h2>

<p>Start with a normal <code>.env.local</code>, then split it:</p>

<ul>
  <li><code>.env.local</code>: safe local config, feature flags, non-secret defaults</li>
  <li><code>.env.local.secrets</code>: secrets only</li>
</ul>

<p>Example:</p>

<pre><code class="language-dotenv"># .env.local
BETTER_AUTH_URL=http://localhost:3000
USE_KEYCHAIN_FOR_DOTX=true
</code></pre>

<pre><code class="language-dotenv"># .env.local.secrets
POSTGRES_URL=postgres://...
GOOGLE_CLIENT_SECRET=...
BETTER_AUTH_SECRET=...
</code></pre>

<p>Make sure your <code>.gitignore</code> covers all the pieces:</p>

<pre><code class="language-gitignore">.env.local
.env.local.secrets
.env.keys
</code></pre>

<p>Then encrypt the secrets file:</p>

<pre><code class="language-bash">pnpm exec dotenvx encrypt -f .env.local.secrets
</code></pre>

<p>That gives you an encrypted <code>.env.local.secrets</code> and a decryption key in
<code>.env.keys</code>.</p>

<p>At this point, you have improved at-rest security a bit, but the key is still on
disk, which is not the end state we want.</p>

<h2 id="why-bother-with-the-extra-steps">Why bother with the extra steps?</h2>

<p>There have been enough supply chain and developer tooling incidents lately that I
no longer treat “it is gitignored” as a meaningful security boundary. Once
something malicious lands in your development environment, one of the first
profitable things it can do is read local env files and exfiltrate credentials.</p>

<p>Encrypting local secrets at rest is not a complete defense, but it is a useful
speed bump:</p>

<ul>
  <li>secrets are no longer sitting in plaintext on disk</li>
  <li>the decryption key can live in the OS keychain instead of another dotfile</li>
  <li>accidental repo-wide file reads become less damaging</li>
  <li>you can keep the workflow mostly compatible with existing frameworks</li>
</ul>

<p>This does <strong>not</strong> protect secrets after your app starts. At runtime, the process
still has decrypted environment variables in memory. But that is still better
than leaving everything plaintext on disk all the time.</p>

<h2 id="move-the-key-into-macos-keychain">Move the key into macOS Keychain</h2>

<p>Copy the <code>DOTENV_PRIVATE_KEY_LOCAL_SECRETS</code> value from <code>.env.keys</code>, then store it
in Keychain:</p>

<pre><code class="language-bash">security add-generic-password -U \
  -a LOCAL_SECRETS_DOTENVX_KEY \
  -s LOCAL_SECRETS_DOTENVX_KEY \
  -w
</code></pre>

<p>With <code>security</code>, keeping <code>-w</code> as the last argument makes it prompt you for the
secret instead of putting it in your shell history.</p>

<p>The <code>LOCAL_SECRETS_DOTENVX_KEY</code> label is just an example. Pick any consistent
Keychain item name you want, then use that same name everywhere in your scripts.</p>

<p>Now you can delete <code>.env.keys</code>. Before you do, stash the key somewhere safe
outside the repo. A password manager like 1Password is a good choice. You will
need it if you set up another machine or need to recover.</p>

<p>With that, your decryption key is no longer sitting next to the repo in another
plaintext file.</p>

<blockquote>
  <p><strong>Linux and Windows.</strong> This post uses macOS Keychain, but the same idea
applies elsewhere. On Linux, <code>secret-tool</code> (backed by <code>libsecret</code> and
GNOME Keyring or KWallet) fills the same role. On Windows, you can use
Credential Manager via PowerShell’s <code>Get-StoredCredential</code> /
<code>New-StoredCredential</code> cmdlets. The loading pattern stays the same; only
the key retrieval command changes.</p>
</blockquote>

<h2 id="the-loading-pattern">The loading pattern</h2>

<p>The subtle part is loader order.</p>

<p>If you want a flag like <code>USE_KEYCHAIN_FOR_DOTX=true</code> to live in <code>.env.local</code>,
your app needs to read <code>.env.local</code> <strong>before</strong> it decides whether to pull the
decryption key from Keychain.</p>

<p>That means the loader should do this:</p>

<ol>
  <li>Load <code>.env</code></li>
  <li>Load <code>.env.local</code></li>
  <li>Check <code>USE_KEYCHAIN_FOR_DOTX</code></li>
  <li>If enabled, read <code>DOTENV_PRIVATE_KEY_LOCAL_SECRETS</code> from Keychain</li>
  <li>Load <code>.env.local.secrets</code></li>
</ol>

<p>Here is the core idea in TypeScript:</p>

<p><a href="https://gist.github.com/ustun/1f5a9974394cc32bba066e5584243ada">Open as GitHub Gist</a></p>

<pre><code class="language-ts">import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { config } from "@dotenvx/dotenvx";

export function loadEnv(): void {
  for (const file of [".env", ".env.local"]) {
    const path = resolve(process.cwd(), file);
    if (existsSync(path)) {
      config({ path });
    }
  }

  const localSecretsPath = resolve(process.cwd(), ".env.local.secrets");
  if (!existsSync(localSecretsPath)) {
    return;
  }

  if (process.env.USE_KEYCHAIN_FOR_DOTX === "true") {
    try {
      process.env.DOTENV_PRIVATE_KEY_LOCAL_SECRETS = execFileSync(
        "security",
        [
          "find-generic-password",
          "-a",
          "LOCAL_SECRETS_DOTENVX_KEY",
          "-s",
          "LOCAL_SECRETS_DOTENVX_KEY",
          "-w",
        ],
        { encoding: "utf-8" },
      ).trim();
    } catch (err) {
      throw new Error(
        "Failed to read decryption key from macOS Keychain. " +
          "Make sure the LOCAL_SECRETS_DOTENVX_KEY item exists. " +
          "See scripts/store-keychain-key.sh for setup.",
        { cause: err },
      );
    }
  }

  config({ path: localSecretsPath, overload: true });

  // The private key has done its job. Remove it from the environment so it
  // is not visible in process.env dumps or child process inheritance.
  delete process.env.DOTENV_PRIVATE_KEY_LOCAL_SECRETS;
}
</code></pre>

<p>Three notes:</p>

<ul>
  <li><code>execFileSync</code> will throw on a non-zero exit, so the try/catch above turns
a cryptic child-process error into a clear setup instruction</li>
  <li>the <code>delete</code> at the end matters: once <code>dotenvx</code> has decrypted the secrets
into their individual env vars, the private key has no further purpose; leaving
it in <code>process.env</code> means any code that inspects the environment (logging,
diagnostics, error reporters) could leak the one key that decrypts the file</li>
  <li>do not log even partial key material during startup. That is easy to get
wrong when debugging the integration</li>
</ul>

<p>One thing this does <strong>not</strong> protect against: once your app is running, the
decrypted secrets are plain strings in <code>process.env</code>. Anyone who can attach a
Node debugger to your process can inspect memory directly.</p>

<p>Cross-process env snooping is more nuanced. On macOS 11+, SIP prevents
processes from reading other processes’ environment variables, so this vector
is largely closed on a default macOS install. On Linux, <code>/proc/&lt;pid&gt;/environ</code>
is still readable by any process running as the same user. Either way, this
pattern is about secrets at rest on disk, not secrets in a running process.</p>

<h2 id="nextjs-integration">Next.js integration</h2>

<p>If you are using Next.js, you cannot just call <code>loadEnv()</code> from anywhere and
expect it to work. Next.js has its own env loading built in, and by the time
your application code runs, it has already resolved which variables are
available.</p>

<p>The right place to hook this in is <code>instrumentation.ts</code> (or <code>.js</code>). Next.js
calls the <code>register</code> function exported from this file once when the server
starts, before any routes or middleware run. That makes it the earliest
reliable point to pull secrets from Keychain and inject them into <code>process.env</code>.</p>

<pre><code class="language-ts">// instrumentation.ts
export async function register() {
  const { loadEnv } = await import("./lib/load-env");
  loadEnv();
}
</code></pre>

<p>The dynamic import is intentional. It keeps the Keychain and <code>dotenvx</code> logic
out of the client bundle and avoids top-level side effects that could run at
the wrong time.</p>

<p>Make sure <code>instrumentation.ts</code> is at your project root (next to <code>next.config</code>),
and that you are on Next.js 15+ where the instrumentation hook is stable. On
older versions (13.2 through 14.x) it works but requires setting
<code>experimental.instrumentationHook: true</code> in your Next config.</p>

<h2 id="helper-scripts-for-temporary-decryptre-encrypt">Helper scripts for temporary decrypt/re-encrypt</h2>

<p>I also like keeping two tiny helper scripts around so I can temporarily decrypt
the file, edit it, and then re-encrypt it.</p>

<pre><code class="language-bash">#!/usr/bin/env bash
# scripts/decrypt-local-secrets.sh
set -euo pipefail

KEYCHAIN_ITEM_NAME="LOCAL_SECRETS_DOTENVX_KEY"

export DOTENV_PRIVATE_KEY_LOCAL_SECRETS="$(
  security find-generic-password \
    -a "$KEYCHAIN_ITEM_NAME" \
    -s "$KEYCHAIN_ITEM_NAME" \
    -w
)"

pnpm exec dotenvx decrypt -f .env.local.secrets
</code></pre>

<pre><code class="language-bash">#!/usr/bin/env bash
# scripts/encrypt-local-secrets.sh
set -euo pipefail

KEYCHAIN_ITEM_NAME="LOCAL_SECRETS_DOTENVX_KEY"

export DOTENV_PRIVATE_KEY_LOCAL_SECRETS="$(
  security find-generic-password \
    -a "$KEYCHAIN_ITEM_NAME" \
    -s "$KEYCHAIN_ITEM_NAME" \
    -w
)"

pnpm exec dotenvx encrypt -f .env.local.secrets
</code></pre>

<p>That gives you a simple workflow:</p>

<pre><code class="language-bash">bash scripts/decrypt-local-secrets.sh
# edit .env.local.secrets
bash scripts/encrypt-local-secrets.sh
</code></pre>

<p>One risk here: if you decrypt the file, edit it, and forget to re-encrypt,
your secrets are back to sitting in plaintext. A git pre-commit hook can catch
this. Something like:</p>

<pre><code class="language-bash">#!/usr/bin/env bash
# .husky/pre-commit or .git/hooks/pre-commit
if head -c 50 .env.local.secrets 2&gt;/dev/null | grep -qv "^#/"; then
  echo "ERROR: .env.local.secrets appears to be decrypted. Run:"
  echo "  bash scripts/encrypt-local-secrets.sh"
  exit 1
fi
</code></pre>

<p>(<code>dotenvx</code>-encrypted files start with a comment header like
<code>#/-------------------[DOTENV]--------------------/</code>, so checking for the
absence of that prefix is a reasonable heuristic.)</p>

<h2 id="where-this-helps-and-where-it-doesnt">Where this helps, and where it doesn’t</h2>

<p>This pattern helps with:</p>

<ul>
  <li>raising the cost of supply chain attacks that look for <code>.env</code> files</li>
  <li>repo-wide local file scraping</li>
  <li>accidental plaintext secret exposure in local tooling</li>
  <li>reducing how many places secrets live on disk</li>
  <li>avoiding accidental exposure during screen sharing and pair programming</li>
</ul>

<p>It does not solve:</p>

<ul>
  <li>malicious code running inside your process</li>
  <li>a debugger attached to your Node process (secrets are in memory as plain strings)</li>
  <li>cross-process env snooping on Linux (<code>/proc/&lt;pid&gt;/environ</code>); macOS SIP blocks
this since Big Sur, but Linux does not</li>
  <li>secrets already exported into your shell</li>
  <li>logs or copy/paste leaks</li>
  <li>production secret management</li>
</ul>

<p>Think of it as one useful layer, not as a silver bullet.</p>

<h3 id="a-note-on-screen-sharing">A note on screen sharing</h3>

<p>If your secrets live in a
plain-text <code>.env.local</code>, it is very easy to accidentally flash them on screen
during a Zoom call, a pair programming session, or a live demo. All it takes
is opening the wrong file, running <code>cat</code> on it, or having your editor preview
it in a sidebar.</p>

<p>With encrypted <code>.env.local.secrets</code>, that file is just opaque ciphertext. Even
if you open it on camera, nobody sees your database credentials or API keys.
The decryption only happens at runtime, in memory, when your app starts, not
when a human or a screen recording is looking at your filesystem.</p>

<p>This is not a reason to adopt the pattern on its own, but it is a nice side
effect that has already saved me at least once.</p>

<h2 id="the-developer-experience-bar-matters">The developer-experience bar matters</h2>

<p>The reason I like this approach is that it is security work people may actually
keep using.</p>

<p>Once set up, the workflow is close to normal local development:</p>

<ul>
  <li>keep config in <code>.env.local</code></li>
  <li>keep secrets in <code>.env.local.secrets</code></li>
  <li>let the app pull the key from Keychain when needed</li>
</ul>

<p>That is much more likely to stick than a system that feels “more secure” on paper
but creates enough friction that everyone bypasses it.</p>

<h2 id="if-you-want-to-adopt-this">If you want to adopt this</h2>

<p>My suggestions:</p>

<ol>
  <li>Start with local-only encryption, not a big secret-sharing redesign.</li>
  <li>Separate non-secret config from secrets first.</li>
  <li>Make the Keychain path opt-in with a clear env flag.</li>
  <li>Ensure <code>.env.local</code> loads before you evaluate that flag.</li>
  <li>Audit helper scripts too, not just the main app boot path.</li>
  <li>Back up your decryption key in a password manager before deleting <code>.env.keys</code>.</li>
  <li>Add a pre-commit hook to catch unencrypted secrets files.</li>
  <li>Never print keys, even partially, while debugging the integration.</li>
</ol>

<p>That last point deserves repeating.</p>

<p>Security improvements have a way of being partially undone by “temporary”
debugging statements.</p>

<h2 id="related-tools-worth-looking-at">Related tools worth looking at</h2>

<p>If this pattern feels too lightweight for your needs, or you want something
more structured, there are good adjacent tools in this space.</p>

<p><a href="https://getsops.io/">SOPS</a> is a strong option when you want encrypted files as
a first-class workflow, especially in teams already comfortable with cloud KMS,
age, or GitOps-style config management.</p>

<p><a href="https://dmno.dev/">DMNO</a> goes in a different direction: schema-aware config,
tooling around developer experience, and integrations with external secret
stores. Their <a href="https://dmno.dev/docs/plugins/1password/">1Password plugin</a>
is a good example if you want local development ergonomics tied more directly to
a secrets manager instead of local encrypted <code>.env</code> files.</p>

<p>I do not see these as mutually exclusive with the smaller pattern in this post.
They just sit at different points on the complexity and capability curve.</p>

<h2 id="closing-thought">Closing thought</h2>

<p>I do not think local <code>.env</code> files are going away anytime soon.</p>

<p>But I do think the threat model around them has changed.</p>

<p>A small amount of structure, encryption at rest, and OS-managed key storage can
go a surprisingly long way without making local development miserable.</p>

<p><strong>Followup</strong>: I also wrote a companion script that scans your machine for plaintext secrets sitting in .env files. It pairs well with this post as a way to find what needs encrypting.
<a href="https://dev.to/ustun/find-plaintext-secrets-hiding-in-your-env-files-5dpl">Find Plaintext Secrets Hiding in Your .env Files</a></p>]]></content><author><name>Ustun Ozgur</name></author><category term="blog" /><summary type="html"><![CDATA[A defense layer against increasing supply chain attacks that read your .env files]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-14.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-14.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Stop .env Drift Before Merge with Wizard of Drift</title><link href="https://dotenvx.com/blog/2026/03/02/wizard-of-drift-oz-and-dotenvx.html" rel="alternate" type="text/html" title="Stop .env Drift Before Merge with Wizard of Drift" /><published>2026-03-02T00:00:00+00:00</published><updated>2026-03-02T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2026/03/02/wizard-of-drift-oz-and-dotenvx</id><content type="html" xml:base="https://dotenvx.com/blog/2026/03/02/wizard-of-drift-oz-and-dotenvx.html"><![CDATA[<p>I shipped a small GitHub Action this week: <a href="https://github.com/dotenvx/wizard-of-drift">wizard-of-drift</a>.</p>

<p>It catches <code>.env*</code> key drift in pull requests and leaves a review comment with concrete fixes.</p>

<p>It uses <a href="https://warp.dev/oz">Warp’s Oz</a> under the hood to do the review, and it works great.</p>

<p><img src="https://github.com/user-attachments/assets/b77d8c0f-a96e-4e31-905d-6c76c42882a9" /></p>

<h2 id="why">Why</h2>

<p>Teams with multiple env files (<code>.env</code>, <code>.env.production</code>, <code>.env.staging</code>, etc) slowly drift.</p>

<p>Someone adds <code>TWILIO_API_KEY</code> to one file and forgets the others. CI passes, deploy goes out, and then something breaks in preview or prod.</p>

<p>Wizard of Drift catches that in the PR before merge.</p>

<h2 id="the-real-problem">The Real Problem</h2>

<p>Most env drift bugs are boring and expensive:</p>

<ul>
  <li>local works, preview fails</li>
  <li>preview works, production fails</li>
  <li>one service has a key, another service does not</li>
</ul>

<p>And they are hard to catch in code review because reviewers are focused on app code, not checking every <code>.env*</code> file by hand.</p>

<p>Even worse, the PR diff can hide this. If a new key is added in <code>.env.production</code> only, nobody notices until a runtime path hits missing config.</p>

<p>This is the kind of failure that burns hours for no good reason.</p>

<h2 id="what-it-checks">What It Checks</h2>

<ul>
  <li>Scans <code>.env*</code> files in the repo</li>
  <li>Excludes <code>.env.keys</code> (never commit that)</li>
  <li>Compares key names only (not values)</li>
  <li>Handles <code>DOTENV_PUBLIC_KEY</code> naming rules per file:
    <ul>
      <li><code>.env</code> expects <code>DOTENV_PUBLIC_KEY</code></li>
      <li><code>.env.&lt;target&gt;</code> expects <code>DOTENV_PUBLIC_KEY_&lt;TARGET_UPPERCASE&gt;</code></li>
    </ul>
  </li>
  <li>Posts a PR review summary with missing key lines to add</li>
</ul>

<p><img src="https://github.com/user-attachments/assets/a15a1e49-ac63-47a4-83ff-c71f03fddf83" /></p>

<h2 id="add-it-in-30-seconds">Add It In 30 Seconds</h2>

<pre><code class="language-yaml">name: Env Drift Review

on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: read
  pull-requests: write

jobs:
  env-drift:
    runs-on: ubuntu-latest
    steps:
      - uses: dotenvx/wizard-of-drift@v1
        with:
          warp_api_key: $
          github_token: $
          warp_agent_profile: "" # optional
</code></pre>

<h2 id="why-a-coding-agent-works-well-here">Why A Coding Agent Works Well Here</h2>

<p>This is a great use case for an agent because the input is high-context but bounded:</p>

<ul>
  <li>list of env files</li>
  <li>extracted key sets</li>
  <li>the <code>.env*</code> diff in the PR</li>
  <li>explicit rules about <code>DOTENV_PUBLIC_KEY</code> naming</li>
</ul>

<p>Wizard of Drift builds that context first, then gives the agent (in this case Oz) a tight prompt and asks for one output: a concise review summary with exact keys to add.</p>

<p>That pattern matters.</p>

<p>The agent is not guessing from vague code context. It is reviewing a purpose-built context document generated by CI, then returning actionable output directly into the PR conversation.</p>

<p>So the reviewer sees concrete fixes, not generic AI advice.</p>

<h2 id="the-bigger-idea">The Bigger Idea</h2>

<p>This is the kind of workflow I want more of:</p>

<ul>
  <li>static CI context</li>
  <li>AI agent review</li>
  <li>deterministic output in PR comments</li>
</ul>

<p>And Oz was a great choice here because I wanted to easily trigger a coding agent from a GitHub action. Plus it supports any model and can be monitored from <a href="https://oz.warp.dev">its dashboard</a>.</p>

<p>The combination makes my <code>.env</code> workflows safer and easier to operate at scale. Dotenvx gives you the secure env model and the agent gives you a practical enforcement loop at review time.</p>

<p>Together, they remove a class of annoying config breakages before they merge.</p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[Catch .env key drift on pull requests automatically with Warp's Oz + Dotenvx.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-13.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-13.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Setting Up dotenvx with Next.js</title><link href="https://dotenvx.com/blog/2026/02/17/dotenvx-nextjs.html" rel="alternate" type="text/html" title="Setting Up dotenvx with Next.js" /><published>2026-02-17T00:00:00+00:00</published><updated>2026-02-17T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2026/02/17/dotenvx-nextjs</id><content type="html" xml:base="https://dotenvx.com/blog/2026/02/17/dotenvx-nextjs.html"><![CDATA[<p><em>By <a href="https://tonyvantur.com">Tony Vantur</a></em></p>

<p><strong>If your team is still sharing <code>.env</code> values over Slack, this guide is for you.</strong> <strong>dotenvx</strong> encrypts your <code>.env</code> files so you can commit them directly to git. Secrets travel with your code, versioned and reviewable, but unreadable without the private key.</p>

<p>The tool itself is great. Getting it running on Vercel serverless is not as straightforward as the docs suggest. This tutorial walks through the full setup, including the runtime gotcha that is not well-documented and will cost you time if you hit it unprepared.</p>

<p>By the end you will have: encrypted <code>.env</code> files committed to git, automatic decryption in both local dev and Vercel deployments, and separate keys for preview vs production environments.</p>

<h2 id="why-dotenvx">Why dotenvx</h2>

<p>dotenvx is free, open source, and built by the creator of the original <code>dotenv</code> library. It replaces <strong>dotenv-vault</strong> (deprecated May 2024) with a simpler approach: instead of syncing secrets to a hosted service, it encrypts them in place using ECIES. The encrypted files live in your repo. You manage one private key per environment.</p>

<p>The benefits over the Slack-and-gitignore workflow:</p>

<ul>
  <li><strong>Version-controlled secrets.</strong> Changing a database URL shows up in your git diff. Rollbacks roll back secrets too.</li>
  <li><strong>Instant onboarding.</strong> New teammates clone the repo and get all encrypted env files. Hand them the dev key and they are running.</li>
  <li><strong>Multi-environment by default.</strong> Separate <code>.env</code>, <code>.env.production</code>, and <code>.env.staging</code> files, each with their own key pair.</li>
</ul>

<p>Within a year of release, dotenvx crossed a million weekly npm installs, adopted by PayPal, NASA, Supabase, and AWS. It works. The setup on Vercel just needs a few extra steps.</p>

<h2 id="step-1-install-dotenvx">Step 1: Install dotenvx</h2>

<p>Install it as a <strong>dependency</strong>, not a devDependency. This is important because it needs to be available at runtime in Vercel’s serverless bundle.</p>

<pre><code>npm install @dotenvx/dotenvx
</code></pre>

<h2 id="step-2-encrypt-your-env-files">Step 2: Encrypt Your .env Files</h2>

<p>Run <code>dotenvx encrypt</code> in your project root. This encrypts each value in your <code>.env</code> file individually and generates a key pair. The public key gets added as <code>DOTENV_PUBLIC_KEY</code> inside the <code>.env</code> file. The private key goes into a new file called <code>.env.keys</code> — hold onto this, you will need these keys in Step 5.</p>

<pre><code>dotenvx encrypt
</code></pre>

<p>For a production env file:</p>

<pre><code>dotenvx encrypt -f .env.production
</code></pre>

<p>Add <code>.env.keys</code> to your <code>.gitignore</code>. The encrypted <code>.env</code> and <code>.env.production</code> files should be committed — that is the whole point. But <code>.env.keys</code> holds the private decryption keys and must stay local.</p>

<h2 id="step-3-update-your-scripts">Step 3: Update Your Scripts</h2>

<p>Wrap your dev and build commands with the dotenvx CLI so it decrypts before Next.js runs:</p>

<pre><code class="language-json">{
  "scripts": {
    "dev": "dotenvx run -- next dev",
    "build": "dotenvx run -- next build"
  }
}
</code></pre>

<p>At this point, local dev and builds work. Client-side <code>NEXT_PUBLIC_*</code> variables get inlined at build time. If that is all you need, you could stop here.</p>

<p>But if you have server-side variables like <code>DATABASE_URL</code>, keep going. This is where it gets tricky.</p>

<h2 id="step-4-add-the-instrumentation-file">Step 4: Add the Instrumentation File</h2>

<p>Here is the problem: Vercel serverless functions do not use <code>next start</code>. They invoke your route handlers directly, so the <code>dotenvx run</code> CLI wrapper is not present at runtime. Server-side <code>process.env</code> reads get nothing, and you get errors at runtime due to secrets not being available. The same issue applies to any serverless deployment target — Cloudflare Workers, AWS Lambda, etc. — anywhere the CLI wrapper is not invoked at runtime.</p>

<p>The fix is a Next.js instrumentation file. Since the encrypted <code>.env</code> files are committed to git, they are part of the deployment. You just need to decrypt them when the serverless function starts:</p>

<pre><code class="language-typescript">// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { config } = await import('@dotenvx/dotenvx');

    const vercelEnv = process.env.VERCEL_ENV;

    if (vercelEnv === 'production') {
      config({ path: ['.env.production', '.env'], overload: true });
    } else {
      config({ path: ['.env'], overload: true });
    }
  }
}
</code></pre>

<p>Notice this uses <code>VERCEL_ENV</code>, not <code>NODE_ENV</code>. <code>next build</code> always sets <code>NODE_ENV=production</code> even for preview deploys. <code>VERCEL_ENV</code> correctly distinguishes <code>production</code>, <code>preview</code>, and <code>development</code>.</p>

<h2 id="step-5-add-private-keys-to-vercel">Step 5: Add Private Keys to Vercel</h2>

<p>This is the step that connects everything. The instrumentation file from Step 4 calls <code>dotenvx.config()</code> to decrypt your <code>.env</code> files at runtime. But it needs the private keys to do that. Since the keys are not in git (they are in <code>.env.keys</code> which is gitignored), you need to add them to Vercel’s environment variables manually.</p>

<p>Open your local <code>.env.keys</code> file and copy the values. Then in your Vercel project dashboard, go to <strong>Settings &gt; Environment Variables</strong> and add:</p>

<ul>
  <li><code>DOTENV_PRIVATE_KEY</code> — the key that decrypts <code>.env</code>. Scope it to <strong>Preview</strong> and <strong>Development</strong>.</li>
  <li><code>DOTENV_PRIVATE_KEY_PRODUCTION</code> — the key that decrypts <code>.env.production</code>. Scope it to <strong>Production</strong>.</li>
</ul>

<p>Or if you prefer the CLI, you can set them with the Vercel CLI directly:</p>

<pre><code># Preview and Development
vercel env add DOTENV_PRIVATE_KEY preview development

# Production
vercel env add DOTENV_PRIVATE_KEY_PRODUCTION production
</code></pre>

<p>Without these, the instrumentation file will silently fail to decrypt and your server-side <code>process.env</code> reads will be empty.</p>

<h2 id="step-6-deploy-and-verify">Step 6: Deploy and Verify</h2>

<p>Commit your encrypted <code>.env</code> files and push. In the Vercel build logs, you should see dotenvx injecting from your env files. Test a server-side route that reads <code>process.env</code> to confirm runtime decryption is working.</p>

<p>If you see <code>ECONNREFUSED</code> errors, double check: is <code>@dotenvx/dotenvx</code> in <code>dependencies</code> (not <code>devDependencies</code>)? Is <code>instrumentation.ts</code> at the root of your app? Are the private keys set in Vercel’s dashboard?</p>

<h2 id="the-day-to-day-workflow">The Day-to-Day Workflow</h2>

<p>After setup, managing secrets is just:</p>

<pre><code># Add or update a dev secret
dotenvx set KEY value

# Add or update a production secret
dotenvx set KEY value -f .env.production

# Commit and push — picked up automatically on next deploy
git add .env .env.production &amp;&amp; git commit -m "update secrets"
</code></pre>

<p>No dashboard. No Slack. No sync step.</p>

<h2 id="let-ai-set-it-up">Let AI Set It Up</h2>

<p>Copy this prompt into your AI coding assistant to have it do the setup for you:</p>

<blockquote>
  <p>Set up dotenvx for encrypted environment variables in my Next.js project deployed on Vercel. Follow these steps exactly.</p>

  <p><strong>1. Install</strong> — Install @dotenvx/dotenvx as a DEPENDENCY (not devDependency). It must be in the production bundle because Vercel serverless functions need it at runtime.</p>

  <p><strong>2. Encrypt .env files</strong> — Run <code>dotenvx encrypt</code> to encrypt the .env file in place. For a separate production env file, also run <code>dotenvx encrypt -f .env.production</code>.</p>

  <p><strong>3. Update .gitignore</strong> — Add <code>.env.keys</code> to .gitignore. The encrypted .env and .env.production files SHOULD be committed to git. The .env.keys file with private keys should NOT be committed.</p>

  <p><strong>4. Update package.json scripts</strong> — Wrap dev and build commands with <code>dotenvx run --</code>.</p>

  <p><strong>5. Create instrumentation.ts</strong> — This is the critical step. Vercel serverless functions do NOT use <code>next start</code>. Without this file, server-side process.env reads will be empty. Use <code>VERCEL_ENV</code> (not <code>NODE_ENV</code>) to distinguish environments.</p>

  <p><strong>6. Configure Vercel environment variables</strong> — Add <code>DOTENV_PRIVATE_KEY</code> (Preview/Development) and <code>DOTENV_PRIVATE_KEY_PRODUCTION</code> (Production) from your <code>.env.keys</code> file.</p>

  <p><strong>7. Deploy and verify.</strong></p>

  <p>Reference: https://tonyvantur.com/writing/dotenvx-vercel</p>
</blockquote>

<h2 id="resources">Resources</h2>

<ul>
  <li><a href="https://dotenvx.com/docs/platforms/vercel">dotenvx Vercel Platform Guide</a> — Official docs for using dotenvx with Vercel</li>
  <li><a href="https://dotenvx.com/docs">dotenvx Documentation</a> — Full reference for the dotenvx CLI and API</li>
  <li><a href="https://github.com/dotenvx/dotenvx">dotenvx on GitHub</a> — Source code and issue tracker</li>
  <li><a href="https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation">Next.js Instrumentation</a> — How the <code>register()</code> hook works</li>
</ul>]]></content><author><name>Tony Vantur</name></author><category term="blog" /><summary type="html"><![CDATA[Stop Slacking secrets to your team. A step-by-step guide through the gotchas.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-12.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-12.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rotate NPM Tokens with Dotenvx Ops. Automatically.</title><link href="https://dotenvx.com/blog/2025/12/11/rotate-npm-tokens-with-dotenvx-ops.html" rel="alternate" type="text/html" title="Rotate NPM Tokens with Dotenvx Ops. Automatically." /><published>2025-12-11T00:00:00+00:00</published><updated>2025-12-11T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2025/12/11/rotate-npm-tokens-with-dotenvx-ops</id><content type="html" xml:base="https://dotenvx.com/blog/2025/12/11/rotate-npm-tokens-with-dotenvx-ops.html"><![CDATA[<p><strong>NPM’s new short-lived tokens</strong> strengthen security, but they <strong>make rotation painful.</strong> Every 90 days (or sooner) you have to manually create a fresh token, set it in your CI, and make sure nothing breaks. <sup><a href="#footnote1">1</a></sup></p>

<p><img src="https://github.com/user-attachments/assets/9868574d-2e81-4654-b4bc-b1c66df19784" /></p>

<p>This was a real problem for us. We publish <a href="https://www.npmjs.com/org/dotenvx">64 npm packages</a>, and rotating tokens across all of them by hand was not going to be sustainable. Every expiration meant touching dozens of pipelines and praying the next publish didn’t fail.</p>

<p>So we built a solution. Introducing <a href="https://dotenvx.com/docs/ops/rotate">Dotenvx Rotate</a> - part of Dotenvx Ops.</p>

<h2 id="how-it-works">How It Works</h2>

<p>Install <a href="https://dotenvx.com/ops">dotenvx-ops</a>.</p>

<pre><code class="language-sh">$ curl -sfS https://dotenvx.com/ops | sh
</code></pre>

<p>Run <code>rotate npm connect</code> to connect <a href="https://npmjs.com">npm</a>.</p>

<pre><code class="language-sh">$ dotenvx-ops rotate npm connect
</code></pre>

<p>When prompted enter your npm username and password.</p>

<pre><code class="language-sh">$ dotenvx-ops rotate npm connect
✔ npm username: USERNAME
✔ npm password: PASSWORD
</code></pre>

<p>This opens a local browser session, connecting your npm account.</p>

<p><img src="https://github.com/user-attachments/assets/49ee113b-95dd-4586-87ca-06da3b0f8d20" /></p>

<blockquote>
  <p>IMPORTANT: Note that this is <strong>local only</strong> - this way we can bypass the need for storing your credentials.</p>
</blockquote>

<p>Complete any 2FA steps manually.</p>

<p><img src="https://github.com/user-attachments/assets/a7487c65-0ee6-4415-9a94-d1e64a377e66" /></p>

<p>On success, return to your CLI, and you will see a passcard created.</p>

<pre><code class="language-sh">$ dotenvx-ops rotate npm connect
✔ npm username: USERNAME
✔ npm password: PASSWORD
✔ connected [https://ops.dotenvx.com/go/pas_1234..]
</code></pre>

<p><em>Dotenvx Passcards</em> are special connectors allowing account access.</p>

<p>Next, use the passcard to rotate your npm token.</p>

<h2 id="rotate">Rotate</h2>

<p>Run <code>rotate</code> on the passcard.</p>

<pre><code class="language-sh">$ dotenvx-ops rotate dotenvx://pas_1234..
⠏ rotating..
</code></pre>

<p>It takes 10-30 seconds. On success, it returns a Dotenvx Rotation Token (ROT).</p>

<pre><code class="language-sh">$ dotenvx-ops rotate dotenvx://pas_1234..
✔ rotated [https://ops.dotenvx.com/go/pas_1234..]
⮕ next run [dotenvx-ops get dotenvx://rot_a2c4..]
</code></pre>

<p><em>Dotenvx Rotation Tokens (ROTs)</em> are special tokens that can change value. You can think of them as proxy tokens.</p>

<p>Next, let’s get the value for it.</p>

<h2 id="get">Get</h2>

<p>Run <code>get</code> on the rotation token.</p>

<pre><code class="language-sh">$ dotenvx-ops get dotenvx://rot_a2c4..
npm_d2cJ..
</code></pre>

<p>It returns your npm token. Cool!</p>

<h2 id="rotate-again">Rotate Again</h2>

<p>Run <code>rotate</code> on the passcard again.</p>

<pre><code class="language-sh">$ dotenvx-ops rotate dotenvx://pas_1234..
✔ rotated [https://ops.dotenvx.com/go/pas_1234..]
⮕ next run [dotenvx-ops get dotenvx://rot_a2c4..]
</code></pre>

<p>And <code>get</code> the ROT again.</p>

<pre><code class="language-sh">$ dotenvx-ops get dotenvx://rot_a2c4..
npm_cbGY..
</code></pre>

<p>The value changed. Way cool!</p>

<p>That’s the ROT at work. ROTs introduce a new key rotation primitive: the npm token rotates, the reference does not. This is useful for operations, especially CI/CD.</p>

<h2 id="cicd">CI/CD</h2>

<p>Previously, our CI/CD had <code>npm publish</code> with a hardcoded <code>secrets.NPM_TOKEN</code>:</p>

<pre><code class="language-yaml">npm:
  ...
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: '18.x'
        registry-url: 'https://registry.npmjs.org'
    - run: npm publish
      env:
        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
</code></pre>

<h4 id="step-1">Step 1</h4>

<p>We first replaced <code>secrets.NPM_TOKEN</code> with <code>env.NODE_AUTH_TOKEN</code>.</p>

<pre><code class="language-yaml">npm:
  ...
  runs-on: ubuntu-latest
  steps:
    ...
    - run: npm publish
      env:
        NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }}
</code></pre>

<h4 id="step-2">Step 2</h4>

<p>Then we added a step to:</p>

<ul>
  <li><a href="https://dotenvx.com/docs/ops/install">Install dotenvx-ops</a></li>
  <li>Run <code>dotenvx-ops get</code> to echo its value to <code>NODE_AUTH_TOKEN</code></li>
</ul>

<pre><code class="language-yaml">npm:
  ...
  runs-on: ubuntu-latest
  steps:
    ...
    - run: |
        curl -sfS https://dotenvx.sh/ops | sh
        echo "NODE_AUTH_TOKEN=$(dotenvx-ops get dotenvx://rot_a2c4 --token '${{ secrets.DOTENVX_OPS_TOKEN }}')" &gt;&gt; $GITHUB_ENV
    - run: npm publish
      env:
        NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }}
</code></pre>

<h4 id="step-3">Step 3</h4>

<p>Last, we set <code>DOTENVX_OPS_TOKEN</code> in <a href="https://github.com/username/project/settings/secrets/actions">GitHub Actions Secrets</a> (or GitLab CI, CircleCI, or wherever you run your automated npm publishing).</p>

<p><img src="https://github.com/user-attachments/assets/db12882b-8b35-40db-a62f-238df32ff3f6" /></p>

<p>Tip: Find your <code>DOTENVX_OPS_TOKEN</code> on your <a href="https://ops.dotenvx.com/settings">Dotenvx Settings Page</a>.</p>

<p><img src="https://github.com/user-attachments/assets/df4f6146-5cf9-44a7-9a22-b967d675f3d8" /></p>

<p>On your next CI run, it will inject the latest rotated NPM token and successfully publish your npm module(s).</p>

<p><img src="https://github.com/user-attachments/assets/7a201f23-c255-4d75-a46e-326ddf22f0d9" /></p>

<p>Incredible!</p>

<h2 id="conclusion">Conclusion</h2>

<p>Publishing now works indefinitely with rotating NPM tokens, powered by a new rotation primitive (ROTs) and passcard connectors.</p>

<ul>
  <li><em>NPM token leaked?</em> Just rotate it - all your operations still work.</li>
  <li><em>Employee left who knew the old token?</em> Rotate it - all your operations still work.</li>
  <li><em>NPM token should be rotated every N days for compliance?</em> Put it on a schedule - all your operations still work.</li>
</ul>

<p>This has worked really well for us. If it sounds useful, you can use it too. Sign up for <a href="https://dotenvx.com/ops">Dotenvx Ops</a>.</p>

<blockquote>
  <p>P.S. If you’re running this at enterprise scale with compliance requirements, scheduled rotation, or broader CI/CD concerns, please <a href="mailto:scott@dotenvx.com">get in touch</a>. We’d like to help.</p>
</blockquote>

<hr />

<p><small><sup id="footnote1">1</sup> <a href="https://github.blog/changelog/2025-09-29-strengthening-npm-security-important-changes-to-authentication-and-token-management/">Strengthening npm security: Important changes to authentication and token management</a></small></p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[NPM's new short-lived tokens strengthen security, but they make rotation painful.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-11.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-11.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Dotenvx vs Docker Compose Secrets: Avoiding False Security</title><link href="https://dotenvx.com/blog/2025/04/19/dotenvx-vs-docker-compose-secrets.html" rel="alternate" type="text/html" title="Dotenvx vs Docker Compose Secrets: Avoiding False Security" /><published>2025-04-19T00:00:00+00:00</published><updated>2025-04-19T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2025/04/19/dotenvx-vs-docker-compose-secrets</id><content type="html" xml:base="https://dotenvx.com/blog/2025/04/19/dotenvx-vs-docker-compose-secrets.html"><![CDATA[<p><strong>Managing secrets in containers can be tricky.</strong> Many developers have heard that environment variables might expose secrets, so using <a href="https://docs.docker.com/compose/how-tos/use-secrets/">Docker Compose Secrets</a> sounds safer. But is it?</p>

<p>In practice, Docker Compose secrets are just plaintext files in disguise, which introduces its own risks. Meanwhile, <a href="https://github.com/dotenvx/dotenvx">dotenvx</a> takes a different approach with just-in-time secret injection that avoids leaving sensitive data lying around.</p>

<h2 id="good-idea-unencrypted-reality">Good Idea, Unencrypted Reality</h2>

<p>Docker Compose added a feature called <em>secrets</em> to keep passwords and API keys out of your Dockerfiles and environment variables. The idea sounds good. But here’s the catch: those secrets are <strong>just plaintext files</strong>.</p>

<p>Compose bind-mounts a plaintext file into your container under <code>/run/secrets</code> <sup><a href="#footnote1">1</a></sup>. No encryption.</p>

<p><a href="https://docs.docker.com/compose/how-tos/use-secrets/" target="_blank"><img src="https://github.com/user-attachments/assets/eedb7ab3-9c9a-49b2-9583-ec11ad411783" /></a></p>

<p>As a result, in a running container, an attacker (or malicious process) can simply read the unencrypted file from <code>/run/secrets</code> and obtain your secret – these secret files are mounted world-readable (mode 0444) <sup><a href="#footnote1">1</a></sup>.</p>

<h2 id="plaintext-secrets-at-rest">Plaintext Secrets at Rest</h2>

<p>Why is a plaintext file at rest such a big deal? Because anything stored on disk is one stray commit or backup away from exposure. If you check that secret file into source control by mistake, or if your server gets compromised, the secret is sitting there in plain text.</p>

<p>Docker Compose avoids putting secrets in images or environment variables, but leaving them on the filesystem means they persist longer than they might need to. <strong>Long-lived plaintext secrets are an inviting target.</strong></p>

<p>For example, a common slip-up is a developer accidentally committing a .env or secret file to GitHub – now your passwords are public. Even on a server, a misconfigured web route could unintentionally serve that file, or an attacker could find it among backups.</p>

<p>A secret that lives as plaintext on disk is always at risk of being read by someone unauthorized.</p>

<h2 id="environment-variables">Environment Variables</h2>

<p>On the other hand, <strong>environment variables vanish once the process stops and aren’t directly saved to disk</strong>. But environment variables have their own pitfalls if misused. If you print them in logs or leave them in an .env file on disk, you’re back to square one.</p>

<p>The takeaway here is that <em>how you handle the secret matters more than the mechanism. Simply moving a secret from an env var to a file doesn’t automatically make it safe.</em></p>

<h2 id="dotenvx-just-in-time-secrets-injection">Dotenvx: Just-in-Time Secrets Injection</h2>

<p>Dotenvx approaches secret management with a focus on minimal exposure. It injects your secrets at runtime only, via the command <code>dotenvx run</code>. How does this help?</p>

<p>First, it means <strong>you don’t leave secret values sitting around in a container’s environment or filesystem</strong> for longer than necessary. The dotenvx CLI loads the secrets from an encrypted <code>.env</code> file and injects them as environment variables only while launching your app, then your app uses them in-memory.</p>

<p>There’s no separate plaintext secret file hanging around permanently. In CI/CD, for example, dotenvx will decrypt your secrets and inject them “just in time” as the build or app starts.</p>

<h2 id="dotenvx-encryption">Dotenvx: Encryption</h2>

<p><strong>Crucially, dotenvx lets you encrypt your .env files.</strong> With one command <code>dotenvx encrypt</code>, you transform your <code>.env</code> into an encrypted format.</p>

<p>Even if someone finds that file, they can’t read the secrets without the decryption key. It uses AES-256 encryption with ephemeral keys so that even if the encrypted .env file is exposed, its contents remain secure. <sup><a href="#footnote3">3</a></sup> You can commit the encrypted .env to your repo safely – it’s just gibberish to anyone without the key.</p>

<p>Come runtime, you provide the key (often via an environment variable or a secret manager) and dotenvx seamlessly decrypts and injects the real values into your app. The end result: <em>no plaintext secrets sitting at rest on your disk or in your container. They exist only in memory when needed.</em></p>

<h2 id="how-it-works">How It Works</h2>

<p>You run your app like this:</p>

<pre><code>$ dotenvx run -- node app.js
</code></pre>

<p>Secrets are decrypted and loaded as environment variables <em>just for that process</em>. Nothing written to disk. No lingering files in containers.</p>

<p>Want to commit secrets to your repo? Encrypt them first:</p>

<pre><code>$ dotenvx encrypt
</code></pre>

<p>Then you can safely version your encrypted <code>.env</code> file. It’s useless without the decryption key.</p>

<p>It’s that easy and no plaintext files sitting around! Great!</p>

<h2 id="comparison">Comparison</h2>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Docker Compose Secrets</th>
      <th>Dotenvx</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Secrets encrypted at rest?</td>
      <td>❌ No</td>
      <td>✅ Yes</td>
    </tr>
    <tr>
      <td>Secrets exist on disk?</td>
      <td>✅ Yes (plaintext file)</td>
      <td>❌ No (only in memory)</td>
    </tr>
    <tr>
      <td>Easy to leak by accident?</td>
      <td>✅ Yes (file is always there)</td>
      <td>🚫 Much harder (encrypted, ephemeral)</td>
    </tr>
    <tr>
      <td>Can safely commit secrets?</td>
      <td>❌ No</td>
      <td>✅ Yes (if encrypted)</td>
    </tr>
    <tr>
      <td>Works outside containers?</td>
      <td>❌ Not easily</td>
      <td>✅ Yes</td>
    </tr>
  </tbody>
</table>

<h2 id="takeaway">Takeaway</h2>

<p>Docker Compose secrets <em>look</em> safer than env vars — but they aren’t encrypted, and they persist longer. Meanwhile, dotenvx focuses on <strong>short-lived, encrypted, just-in-time secrets</strong>.</p>

<p>If you care about reducing blast radius and limiting exposure, <a href="https://github.com/dotenvx/dotenvx">dotenvx</a> is a solid and modern option to consider.</p>

<p>To learn more, <a href="https://dotenvx.com/dotenvx.pdf">read the whitepaper</a>.</p>

<hr />

<p><small><sup id="footnote1">1</sup> <a href="https://docs.docker.com/reference/compose-file/configs/">docker.com</a></small>
<small><sup id="footnote2">2</sup> <a href="https://news.ycombinator.com/item?id=40798534">news.ycombinator.com</a></small>
<small><sup id="footnote3">3</sup> <a href="https://github.com/dotenvx/dotenvx">github.com/dotenvx/dotenvx</a></small></p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[Why secrets in plaintext files might be more dangerous than environment variables — and how dotenvx helps.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-10.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-10.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Claude MCP + Dotenvx</title><link href="https://dotenvx.com/blog/2025/04/01/claude-mcp-dotenvx.html" rel="alternate" type="text/html" title="Claude MCP + Dotenvx" /><published>2025-04-01T00:00:00+00:00</published><updated>2025-04-01T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2025/04/01/claude-mcp-dotenvx</id><content type="html" xml:base="https://dotenvx.com/blog/2025/04/01/claude-mcp-dotenvx.html"><![CDATA[<p class="text-center small">Add Dotenvx to Claude as an MCP Server.</p>

<video class="my-10 w-full rounded-md border border-zinc-200 dark:border-zinc-800" controls="">
<source src="https://github.com/user-attachments/assets/5f974de0-1831-4ae7-a3c8-a724418863db" type="video/mp4" />
your browser does not support the video tag
</video>

<p>Dotenvx works with <a href="https://www.anthropic.com/news/model-context-protocol">Claude MCP</a>. Here’s how to get it in your Claude.</p>

<h2 id="step-1">Step 1</h2>

<p>Type <code>claude mcp add</code>.</p>

<pre><code>$ claude mcp add
Add MCP Server

Choose a unique name for this server:

&gt; dotenvx

</code></pre>

<h2 id="step-2">Step 2</h2>

<p>For <em>Server Scope</em>, choose <em>User</em>.</p>

<pre><code>Server Scope

Choose where this server will be available:

  
  Project (shared via .mcp.json)
  Local (private to you in this project)
&gt; User (available in all your projects)

</code></pre>

<h2 id="step-3">Step 3</h2>

<p>For <em>Transport Type</em>, choose <em>Stdio</em>.</p>

<pre><code>Transport Type

Choose how Claude Code will connect to your MCP server:

&gt; Stdio (command-line process)
  SSE (Server-Sent Events over HTTP)

</code></pre>

<h2 id="step-4">Step 4</h2>

<p>Enter <code>npx @dotenvx/dotenvx</code> as the server command.</p>

<pre><code>Server Command

Enter the full command to start your MCP server.

&gt; npx @dotenvx/dotenvx

</code></pre>

<p>Confirm that. That’s it!</p>

<p>Now you can ask Claude to <em>encrypt my .env file</em> and it will do it!</p>

<blockquote>
  <p>Hey Claude, encrypt my .env file please.</p>
</blockquote>

<h2 id="bonus">Bonus</h2>

<p>Additionally, be sure to share <a href="https://dotenvx.com/llms.txt">llms.txt</a> and <a href="https://dotenvx.com/llms-full.txt">llms-full.txt</a> with Claude to make it smarter about all this.</p>

<p>Thanks for using Dotenvx.</p>

<hr />

<p>If you enjoyed this post, please <a href="https://github.com/dotenvx/dotenvx">share dotenvx with friends</a> or <a href="https://github.com/dotenvx/dotenvx">star it on GitHub</a> to help spread the word.</p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[Add Dotenvx as a Claude MCP Server.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-9.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-9.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">1 Million Installs: From Experimental to Essential</title><link href="https://dotenvx.com/blog/2025/03/03/one-million-installs.html" rel="alternate" type="text/html" title="1 Million Installs: From Experimental to Essential" /><published>2025-03-03T00:00:00+00:00</published><updated>2025-03-03T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2025/03/03/one-million-installs</id><content type="html" xml:base="https://dotenvx.com/blog/2025/03/03/one-million-installs.html"><![CDATA[<p>Eight months ago, dotenvx introduced a new way to think about <a href="/blog/2024/06/24/dotenvx-next-generation-config-management.html">config management</a>. Today, it’s an essential secrets tool with over <a href="https://www.npmjs.com/package/@dotenvx/dotenvx">1 million monthly installs</a> (and growing quickly).</p>

<p><a href="https://npm.chart.dev/@dotenvx/dotenvx" target="_blank"><img src="https://github.com/user-attachments/assets/776ed814-b2d0-49be-a61f-98ed494c7aa3" /></a></p>

<p>That growth didn’t happen by accident—dotenvx development has been heavy with <a href="https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md#1383">88 releases in 8 months</a>. That’s a new release every 2.75 days!</p>

<p>Today, it is a world-class tool–handling <a href="https://dotenvx.com/docs/quickstart">.env parsing</a>, <a href="https://dotenvx.com/docs/advanced/run-variable-expansion">variable expansion</a>, <a href="https://dotenvx.com/docs/advanced/run-command-substitution">command substitution</a>, <a href="https://dotenvx.com/docs/env-file#encryption">encrypted .env files</a>, and <a href="https://dotenvx.com/docs/advanced">more</a> better than anything else can. Each day, hundreds of software engineers newly learn of, and begin using, dotenvx.</p>

<p><img src="https://github.com/user-attachments/assets/1297f2d5-579d-4894-8bc2-91f0f90e4f91" /></p>

<p>As a result, it’s starting to grow into something bigger—<strong>a community, an ecosystem, and a standard.</strong></p>

<h2 id="community">Community</h2>

<p>Dotenvx has grown fast, and a big part of that is developer involvement. In just eight months, engineers have contributed ideas, issues, and pull requests that shaped its direction.</p>

<ul>
  <li><strong>GitHub Activity.</strong> More than <a href="https://github.com/dotenvx/dotenvx/issues">250 issues filed</a>, <a href="https://github.com/dotenvx/dotenvx/pulls">275 PRs created</a>, and <a href="https://github.com/user-attachments/assets/1258fd84-165c-4947-b13b-ffeb49822860">60,000 lines of code written</a> in the last eight months.</li>
  <li><strong>Third-Party Documentation.</strong> Users <a href="https://medium.com/@t.dekiere/boost-dx-with-dotenvx-71e276dce6f6">wrote</a> <a href="https://parottasalna.com/2024/06/30/migrating-from-env-to-dotenvx/">about</a> <a href="https://dev.to/this-is-learning/exploring-dotenvx-46ng">dotenvx</a>, made <a href="https://youtu.be/xcBHX2m2pNw?t=102">videos</a> <a href="https://www.youtube.com/watch?v=1p2MS8rKHzU&amp;t=3s">about</a> <a href="https://www.youtube.com/watch?v=APhfQ2xya9A">dotenvx</a>, <a href="https://www.reddit.com/r/webdev/comments/1gteux5/comment/lxmhzue/">recommended</a> <a href="https://www.reddit.com/r/Python/comments/1gud1h9/comment/lxtv16s/">dotenvx</a>, and <a href="https://railway.com/template/zXEiVF">much</a> <a href="https://forums.docker.com/t/docker-compose-argument-to-replace-env-file-directive-or-argument-to-enable-host-environment-passthrough/141671">more</a>.</li>
  <li><strong>Adoption and Dependents.</strong> Most significantly, major projects adopted dotenvx and became <a href="https://github.com/dotenvx/dotenvx/network/dependents">dotenvx dependents</a>.
    <ul>
      <li><a href="https://github.com/nasa/earthdata-search">NASA</a></li>
      <li><a href="https://docs.amplify.aws/nextjs/deploy-and-host/fullstack-branching/secrets-and-vars/#local-environment-2">AWS</a></li>
      <li><a href="https://github.com/supabase/supabase/blob/master/examples/slack-clone/nextjs-slack-clone-dotenvx/README.md">Supabase</a></li>
      <li><a href="https://github.com/cloudflare/templates">Cloudflare</a></li>
    </ul>
  </li>
</ul>

<p><img src="https://github.com/user-attachments/assets/7cb15832-8e91-494b-b825-4fe0f54fbbed" /></p>

<p>This kind of community involvement makes dotenvx better. The feedback loop between users and development is tight, and that’s been key to its fast growth.</p>

<h2 id="ecosystem">Ecosystem</h2>

<p>Dotenvx isn’t just a CLI—it’s extending into the tools developers use every day.</p>

<ul>
  <li><strong>VS Code Extension.</strong> Decrypt your encrypted .env files in VS Code with the <a href="https://dotenvx.com/vscode-extension/">Dotenvx VS Code Extension</a>.</li>
  <li><strong>Chrome Extension.</strong> Decrypt your encrypted .env files directly on GitHub with the <a href="https://dotenvx.com/chrome-extension">Dotenvx Chrome Extension</a>.</li>
  <li><strong>Buildpacks.</strong> Install dotenvx to Heroku (or any platform that supports buildpacks) with the <a href="https://github.com/dotenvx/heroku-buildpack-dotenvx">Dotenvx Buildpack</a>.</li>
</ul>

<p><a href="https://dotenvx.com/mods"><img src="https://github.com/user-attachments/assets/70047aa8-3510-463a-8858-d27a05e8777f" /></a></p>

<p>This growing ecosystem ensures that no matter where developers work—local editors, browsers, or cloud platforms—dotenvx is increasingly there to seamlessly handle secrets.</p>

<h2 id="standard">Standard</h2>

<p>Dotenvx isn’t just widely used—it’s built on a foundation that makes it the right way to handle secrets.</p>

<p>From the <a href="https://dotenvx.com/dotenvx.pdf">whitepaper</a>:</p>

<blockquote>
  <p><strong>Dotenvx: Reducing Secrets Risk with Cryptographic Separation</strong></p>

  <p><strong>Abstract.</strong> An ideal secrets solution would not only centralize secrets but also contain the fallout of a breach. While secrets managers offer centralized storage and distribution, their design creates a large blast radius, risking exposure of thousands or even millions of secrets. We propose a solution that reduces the blast radius by splitting secrets management into two distinct components: an encrypted secrets file and a separate decryption key.</p>
</blockquote>

<p>This approach—cryptographic separation—ensures that even if one component is compromised, the overall security of secrets remains intact. The whitepaper breaks down the problem and solution, and “it makes a great case for dotenvx.” <sup><a href="https://github.com/dotenvx/dotenvx/issues/537#issue-2881322629">1</a></sup></p>

<iframe src="/dotenvx.pdf?v=20260418051940" class="w-[700px] aspect-[8.5/11] max-w-full max-h-screen border-0 mx-auto"></iframe>

<p>By stating the problem well and implementing good <abbr title="Developer Experience">DX</abbr>, dotenvx is emerging as a new standard for secrets management of .env files. It’s pretty incredible.</p>

<h2 id="conclusion">Conclusion</h2>

<p>What started as a rethink of .env files has quickly turned into something much bigger. In just eight months, dotenvx has gone from an emerging idea to an essential tool, trusted by developers and major platforms alike.</p>

<p>With over <strong>1 million monthly installs</strong>, an active and engaged <strong>community</strong>, an expanding <strong>ecosystem</strong>, and a well-defined <strong>standard</strong>, dotenvx isn’t just growing—it’s reshaping how secrets are managed in modern development.</p>

<p>And this is only the beginning. 💪</p>

<hr />

<p>If you enjoyed this post, please <a href="https://github.com/dotenvx/dotenvx">share dotenvx with friends</a> or <a href="https://github.com/dotenvx/dotenvx">star it on GitHub</a> to help spread the word.</p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[Eight months ago, dotenvx was just an experiment. Today, it's an essential tool with over 1 million monthly installs.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-8.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-8.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">It adds up</title><link href="https://dotenvx.com/blog/2024/11/28/thanksgiving.html" rel="alternate" type="text/html" title="It adds up" /><published>2024-11-28T00:00:00+00:00</published><updated>2024-11-28T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2024/11/28/thanksgiving</id><content type="html" xml:base="https://dotenvx.com/blog/2024/11/28/thanksgiving.html"><![CDATA[<p>It’s Thanksgiving 2024 🦃, and I am so thankful.</p>

<p>Last year around this time, I launched the first few versions of dotenvx.</p>

<p>I remember we said goodbye to family - after a wonderful Thanksgiving meal - and headed on a road trip to Arizona to visit my longtime friend, Will VanOosting.</p>

<p><img src="https://github.com/user-attachments/assets/04357139-687d-41ff-8851-c57112a577fa" /></p>

<p>We’ve been friends since kids - playing pony league baseball. As teenagers we used to go to the Temecula mall, get girls’ numbers, and then invite them to the pool hall later. Will cleaned up. He was a real lady’s man!</p>

<p>As we grew older, he pursued a life of adventure, trucking across the country, and I pursued a creative life, making software at startups. We always stayed in touch.</p>

<p><img src="https://github.com/user-attachments/assets/c7054b33-954f-47af-a059-9b40417a1936" /></p>

<p>Anyway, we left Thanksgiving and headed toward Kingman, Arizona. But before visiting Will and his family, we headed further to catch an old train for the Grand Canyon.</p>

<p><img src="https://github.com/user-attachments/assets/8b6d3898-d19c-40be-99ef-33863cc7ea40" /></p>

<p><img src="https://github.com/user-attachments/assets/4bfde9b2-9b78-4008-bebe-3997e18ed9be" /></p>

<p>I remember it was cold, and we got ‘robbed’ on the train by cowboys on horses. It was a real adventure for the kids. For their souvenirs they each chose a stuffed animal. Those two little stuffed animals have become like part of the family - joining us on so many trips. ‘Bambi’ the deer - Sophia’s choice. And ‘Frost’ the wolf - Charlie’s.</p>

<p><img src="https://github.com/user-attachments/assets/b14fc047-9817-4939-a239-292d963d56d6" /></p>

<video class="w-full rounded-md" controls="">
  <source src="https://github.com/user-attachments/assets/9caf3e7d-2fd5-428b-af95-381ae9958573" type="video/mp4" />
  Your browser does not support the video tag
</video>

<p>We returned from the Grand Canyon and spent the next day hanging out with Will’s family. It was great to catch up. His parents were also in town, and I hadn’t seen them in probably a decade. Somehow we all ended up at a pool hall at the end of the day.</p>

<p><img src="https://github.com/user-attachments/assets/615b673b-289f-4830-a0e9-786337664d9b" /></p>

<p><img src="https://github.com/user-attachments/assets/4c78a3be-f5e6-40d1-9dc1-563d28463f9a" /></p>

<p>Good memories, that trip.</p>

<p>What I don’t remember is late nights and early mornings writing code. Yet, dotenvx’s git history says I did.</p>

<p><img src="https://github.com/user-attachments/assets/033e15db-961c-45cb-af39-0c563bfac160" /></p>

<p><img src="https://github.com/user-attachments/assets/c8097492-daf1-4ec5-88e9-fe18b64d76be" /></p>

<p>That was over 130,000 lines of code ago.</p>

<p>Over 130,000 lines of code.</p>

<p>Incredible.</p>

<p><strong>Probably that part was hard.</strong> Because as you know it’s tough carving out an hour here, 30 minutes there, to pursue a dream.</p>

<p><strong>But it adds up.</strong></p>

<p>And as I reflect back on the last year, I am thankful for that hard work and enormously thankful for the many people who chose to take a chance on dotenvx, and now, use it daily. Thank you so much. 🙏</p>

<p>But most of all, I’m thankful for family, friends, and the memories we’ve had together. They are cherished like gold.</p>

<p>Happy Thanksgiving all! 🦃</p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[It's tough carving out an hour here, 30 minutes there, to pursue a dream. But it adds up.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-7.jpg" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-7.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I know kung fu - llms.txt</title><link href="https://dotenvx.com/blog/2024/11/19/llms-txt.html" rel="alternate" type="text/html" title="I know kung fu - llms.txt" /><published>2024-11-19T00:00:00+00:00</published><updated>2024-11-19T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2024/11/19/llms-txt</id><content type="html" xml:base="https://dotenvx.com/blog/2024/11/19/llms-txt.html"><![CDATA[<p>“I know kung fu”. Neo said that. Maybe you remember the <a href="https://www.youtube.com/watch?v=0YhJxJZOWBw">scene</a>. Today, you can similarly upgrade your llm with a simple txt file.</p>

<p>In response to that, we’re one of the first software projects worldwide to offer that ability to devs. If you’re using <a href="https://dotenvx.com">dotenvx</a> we’ve created the following files for your llm consumption:</p>

<ul>
  <li><a href="https://dotenvx.com/llms.txt"><code>llms.txt</code></a></li>
  <li><a href="https://dotenvx.com/llms-full.txt"><code>llms-full.txt</code></a>.</li>
</ul>

<p>They are plain text files:</p>

<pre><code># dotenvx

&gt; Secrets for agents

## Docs

- [Advanced](https://dotenvx.com/docs/advanced.html): Advanced usage and commands for `dotenvx`
- [dotenvx ext genexample directory](https://dotenvx.com/docs/advanced/genexample-directory.html): Generate a .env.example file inside the specified directory. Useful for monorepos.
- [dotenvx ext genexample -f](https://dotenvx.com/docs/advanced/genexample-f.html): Pass multiple .env files to generate your .env.example file from the combination of their contents.
- [dotenvx ext genexample](https://dotenvx.com/docs/advanced/genexample.html): Generate a .env.example file from your current .env file contents.
- [dotenvx get --all --pretty-print](https://dotenvx.com/docs/advanced/get-all-pretty-print.html): Make the output more readable - pretty print it.
- [dotenvx get --all](https://dotenvx.com/docs/advanced/get-all.html): Return preset machine envs as well.
- [dotenvx get --format eval](https://dotenvx.com/docs/advanced/get-eval.html): Return an eval-ready shell formatted response of all key/value pairs in a .env file.
- [dotenvx get](https://dotenvx.com/docs/advanced/get-json.html): Return a json response of all key/value pairs in a .env file.
- [dotenvx get KEY --convention=nextjs](https://dotenvx.com/docs/advanced/get-key-convention.html): Return a single environment variable's value using the Next.js convention
- [dotenvx get KEY --env](https://dotenvx.com/docs/advanced/get-key-env.html): Return a single environment variable's value from a --env string.
- [dotenvx get KEY -f](https://dotenvx.com/docs/advanced/get-key-f.html): Return a single environment variable's value from a specific .env file.
- [dotenvx get KEY --overload](https://dotenvx.com/docs/advanced/get-key-overload.html): Return a single environment variable's value where each found value is overloaded.
- [dotenvx get KEY](https://dotenvx.com/docs/advanced/get-key.html): Return a single environment variable's value.
- [dotenvx get --format shell](https://dotenvx.com/docs/advanced/get-shell.html): Return a shell formatted response of all key/value pairs in a .env file.
- [dotenvx keypair -f](https://dotenvx.com/docs/advanced/keypair-f.html): Print public/private keys for chosen .env* files.
- [dotenvx keypair KEY](https://dotenvx.com/docs/advanced/keypair-key.html): Print specific keypair for .env file.
- [dotenvx keypair --format shell](https://dotenvx.com/docs/advanced/keypair-shell.html): Print a shell formatted response of public/private keys.
- [dotenvx keypair](https://dotenvx.com/docs/advanced/keypair.html): Print public/private keys for .env file.
- [dotenvx ls directory](https://dotenvx.com/docs/advanced/ls-directory.html): Print all .env files inside a specified path to a directory.
- [dotenvx ls -ef](https://dotenvx.com/docs/advanced/ls-ef.html): Glob .env filenames excluding a wildcard.
- [dotenvx ls -f](https://dotenvx.com/docs/advanced/ls-f.html): Glob .env filenames matching a wildcard.
- [dotenvx ls](https://dotenvx.com/docs/advanced/ls.html): Print all .env files in a tree structure.
- [dotenvx run - Command Substitution](https://dotenvx.com/docs/advanced/run-command-substitution.html): Add the output of a command to one of your variables in your .env file.
...
</code></pre>

<p>But they can greatly improve your Cursor, ChatGPT, and LLM experience.</p>

<p>You can instruct ChatGPT or Cursor as easily as this:</p>

<p><img src="https://github.com/user-attachments/assets/ae110da9-451e-4529-9d03-6ae70e6827b3" /></p>

<p>That’s it. We hope it helps improve your coding experience ever so slightly. May you <strong>learn kung fu</strong>!</p>

<hr />

<p>P.S. If you want to generate your own llms.txt file we wrote <a href="https://github.com/dotenvx/llmstxt">npx llmstxt</a> to easily generate an <code>llms.txt</code> file from your <code>sitemap.xml</code>.</p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[Teach Cursor, ChatGPT, and other LLM editors kung fu via an llms.txt file.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-6.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-6.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">1,000+ Open Source Projects rely on dotenvx</title><link href="https://dotenvx.com/blog/2024/09/12/from-zero-to-1000.html" rel="alternate" type="text/html" title="1,000+ Open Source Projects rely on dotenvx" /><published>2024-09-12T00:00:00+00:00</published><updated>2024-09-12T00:00:00+00:00</updated><id>https://dotenvx.com/blog/2024/09/12/from-zero-to-1000</id><content type="html" xml:base="https://dotenvx.com/blog/2024/09/12/from-zero-to-1000.html"><![CDATA[<p><strong>Over 1,000 open source projects now rely on <a href="https://github.com/dotenvx/dotenvx">dotenvx</a></strong>. It’s truly a pleasure to see such community adoption. 💫</p>

<p><a href="https://github.com/dotenvx/dotenvx/network/dependents"><img src="https://github.com/user-attachments/assets/005f5da8-3b36-4c19-b2f4-7eb5847353fa" /></a></p>

<blockquote>
  <p>It’s a far cry from <a href="http://github.com/motdotla/dotenv">dotenv’s</a> 18.7 Million, but it’s growing fast - faster than dotenv initially grew. - Mot</p>
</blockquote>

<p>Here’s some notable and interesting projects using <a href="https://github.com/dotenvx/dotenvx">dotenvx</a>.</p>

<ul>
  <li>vxrn ⚛️</li>
  <li>CocosEngine 🎮</li>
  <li>Stack’s sbtc 💰</li>
  <li>Facebook’s Rapid 👍</li>
  <li>French Government 🇫🇷</li>
</ul>

<h3 id="vxrn---642-">vxrn - 642 🌟</h3>

<p>From the same guy - Nate Wienert - that brought you <a href="https://github.com/tamagui/tamagui">Tamagui</a>, vxrn lets you build and serve your React Native apps using only Vite.</p>

<blockquote>
  <p><a href="https://github.com/universal-future/vxrn">github.com/universal-future/vxrn</a></p>
</blockquote>

<p><a href="https://github.com/universal-future/vxrn"><img src="https://github.com/user-attachments/assets/9afdaabf-cdc5-4b62-80a5-3dbc61000ddd" /></a></p>

<h3 id="cocosengine-docs---297-">CocosEngine Docs - 297 🌟</h3>

<p>Cocos game engine accounts for a substantial portion of the mobile gaming market in China - estimated around 40%. The platform has a community of over 1.7 million developers and uses dotenvx to help power its open source documentation.</p>

<blockquote>
  <p><a href="https://github.com/cocos/cocos-docs">github.com/cocos/cocos-docs</a></p>
</blockquote>

<p><a href="https://cocos.com"><img src="https://github.com/user-attachments/assets/27272c24-9f13-4e2f-8d4d-f5a7378ea226" /></a></p>

<h3 id="stacks-sbtc---202-">Stacks sbtc - 202 🌟</h3>

<p>Stacks is the leading Bitcoin L2. They are using dotenvx in their <a href="https://github.com/stacks-network/sbtc">repo containing sbtc</a>.</p>

<blockquote>
  <p><a href="https://github.com/stacks-network/sbtc">github.com/stacks-network/sbtc</a></p>
</blockquote>

<p><a href="https://github.com/stacks-network/sbtc"><img src="https://github.com/user-attachments/assets/b48b8854-eed4-43c2-8cc2-edd9936dd9f8" /></a></p>

<h3 id="facebook-rapid---505-">Facebook Rapid - 505 🌟</h3>

<p>A <a href="https://github.com/facebook">Meta</a> project, Rapid is an OpenStreetMap editing tool using AI.</p>

<blockquote>
  <p><a href="https://github.com/facebook/Rapid">github.com/facebook/Rapid</a></p>
</blockquote>

<p><a href="https://github.com/facebook/Rapid"><img src="https://github.com/user-attachments/assets/5b8cc743-9220-4ced-87a7-5512d76d8500" /></a></p>

<h3 id="french-government----26-">French Government 🇫🇷 - 26 🌟</h3>

<p><a href="https://moncomptepro.beta.gouv.fr">MonComptePro</a> is the French Republic’s new identify provider - providing identity and authorization as a service to French professionals, healthcare providers, and companies. It’s an open sourced service of the state.</p>

<blockquote>
  <p><a href="https://github.com/numerique-gouv/moncomptepro">github.com/numerique-gouv/moncomptepro</a></p>
</blockquote>

<p><a href="https://github.com/universal-future/vxrn"><img src="https://github.com/user-attachments/assets/5dd8d660-28fa-4a52-904d-0799b9dc72eb" /></a></p>

<p class="text-center">Incroyable! Merci aux développeurs d’utiliser <a href="https://github.com/dotenvx/dotenvx">dotenvx</a>. 🍷🥐</p>

<h2 id="what-it-takes-">What it takes 💪</h2>

<p><strong>It takes a lot.</strong></p>

<ul>
  <li>110,000 lines of code written <sup><a href="#footnote1">1</a></sup></li>
  <li>13,777 lines the final result <sup><a href="#footnote2">2</a></sup></li>
  <li>1,658 line README <sup><a href="#footnote3">3</a></sup></li>
  <li>188 versions <sup><a href="#footnote4">4</a></sup></li>
  <li>93 doc pages <sup><a href="#footnote5">5</a></sup></li>
  <li>1 website <sup><a href="#footnote6">6</a></sup></li>
  <li>10 months of labor <sup><a href="#footnote7">7</a></sup></li>
</ul>

<p>Plus, <a href="https://github.com/dotenvx/dotenvx">dotenvx</a> has the advantage of association to <a href="https://github.com/motdotla/dotenv">dotenv</a> - making it easier to get word out! Still it takes a great deal of effort to build a well-adopted developer tool.</p>

<p>I salute all other devs doing the same. We’re playing on hard mode. 🫡</p>

<hr />

<p><small><sup id="footnote1">1</sup> 110,000 lines of code: <img src="https://github.com/user-attachments/assets/28180501-1996-459b-81ac-2ba3a4edcfdc" /></small></p>

<p><small><sup id="footnote2">2</sup> 13,777 lines the final result <img src="https://github.com/user-attachments/assets/879c16af-9eb3-4a52-9d68-4d3aa14722ce" /></small></p>

<p><small><sup id="footnote3">3</sup> 1,658 line README <img src="https://github.com/user-attachments/assets/57868428-01ec-4722-98de-85fee5718be1" /></small></p>

<p><small><sup id="footnote4">4</sup> 188 versions <img src="https://github.com/user-attachments/assets/f6326d1a-e8d4-4ec6-8e18-1023a7ba1982" /></small></p>

<p><small><sup id="footnote5">5</sup> 93 doc pages <img src="https://github.com/user-attachments/assets/d05f1df5-9bf8-4c1e-ae18-27ab5986b088" /></small></p>

<p><small><sup id="footnote6">6</sup> 1 Website <img src="https://github.com/user-attachments/assets/9f94a49c-e327-4e07-bcac-3b011ea457fe" /></small></p>

<p><small><sup id="footnote7">7</sup> 10 months of labor <img src="https://github.com/user-attachments/assets/1dc60adf-5697-443c-abc9-496c9081e5fd" /></small></p>]]></content><author><name>Scott Motte – Mot</name></author><category term="blog" /><summary type="html"><![CDATA[Over 1,000 open source projects rely on dotenvx, and growing.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dotenvx.com/assets/img/blog/blog-5.png" /><media:content medium="image" url="https://dotenvx.com/assets/img/blog/blog-5.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>