# Replace dotenv (Node.js)

[`dotenv`](https://www.npmjs.com/package/dotenv) reads a `.env` file from disk and
copies its `KEY=VALUE` pairs into `process.env`. It's the default way Node projects
load configuration — and it means your real secrets live in a plaintext file that
is one `git add` away from being committed, shared over Slack, or baked into an
image.

seekrit replaces that file. Your secrets are encrypted on your machine and stored
as ciphertext; at runtime the CLI decrypts them locally and injects them into
`process.env` — exactly where your code already reads them. **Your application code
doesn't change**: `process.env.DATABASE_URL` keeps working. You just stop shipping
the plaintext file and delete the `dotenv` dependency.

```diff
- import "dotenv/config";        // reads .env off disk
+ // nothing — seekrit injects process.env before your code runs
```

```bash
- node server.js                 # relied on a committed .env
+ seekrit run -- node server.js  # decrypts & injects, then runs
```

> **Note:** This guide assumes you've done the [Quickstart](/docs/quickstart): installed the CLI, signed in, and created an application and environment. If not, do that first — it takes a few minutes.

## 1. Import your existing `.env`

Bulk-load the file you already have. Each `KEY=VALUE` is encrypted **locally** and
stored under the target environment; the API only ever receives ciphertext. Preview
first with `--dry-run` — it lists which names are new versus updates and writes
nothing:

```bash
seekrit secrets import .env --app storefront --env production --dry-run
seekrit secrets import .env --app storefront --env production
```

The importer understands the same `.env` syntax dotenv does — `export` prefixes,
`#` comments, and single/double quotes. An invalid variable name aborts the whole

> **Tip:** Migrating more than one environment? Import each file into its own environment: `seekrit secrets import .env.development --app storefront --env development`, and so on. Config several apps share belongs in a **group** — see [Environments & groups](/docs/guides/environments).

## 2. Run your app through seekrit

`seekrit run` resolves the environment, decrypts it locally, injects it into the
child process, and then runs your command. Anything after `--` is your command,
verbatim:

```bash
seekrit run --app storefront --env production -- node server.js
seekrit run --app storefront --env production -- npm run dev
```

Confirm a value made it in without printing secrets to your shell history:

```bash
seekrit run --app storefront --env production -- node -e "console.log(!!process.env.DATABASE_URL)"
# true
```

Because seekrit populates `process.env` before your code runs, every
`process.env.X` read works unchanged — there is nothing to load.

## 3. Delete dotenv from your code

Remove the loader line and the dependency. Your `process.env.*` reads stay exactly
as they are.

```diff
- import "dotenv/config";
- // or: require("dotenv").config();

  const dbUrl = process.env.DATABASE_URL;
```

If you preloaded it on the command line (`node -r dotenv/config`) or via a package
script, drop that too, and wrap the script with `seekrit run`:

```diff
  // package.json
  "scripts": {
-   "dev": "node -r dotenv/config server.js",
+   "dev": "node server.js",
    "start": "node server.js"
  }
```

```bash
npm uninstall dotenv dotenv-cli
```

Run the wrapped scripts through the CLI. With a service token in the environment
(next section) it's just `seekrit run -- npm run dev`; interactively, name the
target:

```bash
seekrit run --app storefront --env development -- npm run dev
```

> **Tip:** Don't want to type `seekrit run` every time in local dev? Add a thin script — `"dev:secrets": "seekrit run -- npm run dev"` — or export a service token in your shell profile so a bare `seekrit run -- …` resolves everything.

## 4. Local overrides still use `.env`

You don't lose the convenience of a local file. `seekrit run` auto-loads `.env`
from the working directory and **layers it on top of** the managed secrets, and the
live shell wins over everything:

```
group secrets  <  app-env secrets  <  .env file  <  process env      (highest wins)
```

So you can override a single value locally without touching the shared set — point
a service at a local database, say — while every other variable comes from seekrit:

```bash
echo 'DATABASE_URL=postgres://localhost/dev' > .env.local
seekrit run --env-file .env.local -- npm run dev   # .env.local wins for that one key
```

Not sure where a value resolved from? `--explain` prints each variable's source
layer to stderr (names only, never values):

```bash
seekrit run --app storefront --env development --explain -- true
```

> **Warning:** A leftover committed `.env` will silently **shadow** your managed secrets, since the file layer wins. After importing, delete the committed file and gitignore any local one — see [Clean up](#5-clean-up-the-plaintext-file).

## 5. Clean up the plaintext file

Once secrets are imported and the app runs through `seekrit run`, remove the
plaintext `.env` from the repo and stop tracking it:

```bash
git rm --cached .env
echo '.env' >> .gitignore
echo '.env.local' >> .gitignore
```

> **Warning:** A secret that was ever committed is compromised even after you delete it — it stays in git history. Treat imported values as needing rotation, and reissue any key that lived in a committed `.env`.

## Framework notes

Most Node frameworks read `process.env`, so wrapping their dev/build/start command
with `seekrit run` is all it takes.

- **Next.js** loads its own `.env*` files, but variables already present in
  `process.env` take precedence — so `seekrit run -- next dev` and
  `seekrit run -- next build` just work. Remember Next only exposes
  `NEXT_PUBLIC_`-prefixed vars to the browser; that's unchanged.
- **Vite** exposes only `VITE_`-prefixed vars to client code and reads them from
  `process.env` at config time: `seekrit run -- vite` and
  `seekrit run -- vite build`.
- **TypeScript runners** (`tsx`, `ts-node`) and **nodemon** need no dotenv preload —
  `seekrit run -- tsx watch src/server.ts`, `seekrit run -- nodemon server.js`.
- **Node's built-in `--env-file`** (Node 20.6+) is the same idea as dotenv and can
  likewise go away; let seekrit inject instead of `node --env-file=.env`.

## Production, CI, and containers

In local dev you resolve as yourself. Everywhere else, authenticate with a
**service token** — mint one bound to an environment in the console (or with an
admin token). It carries its own org, app, and environment, self-decrypts with no
passphrase, and can be revoked independently, so `seekrit run` needs no other
flags:

```bash
export SEEKRIT_TOKEN=skt_…
seekrit run -- node server.js
```

For runtime images, skip Node entirely with the static
[`seekrit-run`](/docs/guides/run) launcher — same layering and `.env` parsing, in a
~2 MB dependency-free binary:

```bash
SEEKRIT_TOKEN=skt_… seekrit-run -- node server.js
```

See [Service tokens](/docs/guides/service-tokens) for minting and scoping, and
[CI/CD & containers](/docs/guides/ci-cd) for pipeline and image wiring.

## Next steps

- Share config across services with [Environments & groups](/docs/guides/environments)
- Mint and scope machine credentials with [Service tokens](/docs/guides/service-tokens)
- Go Node-free in containers with the [`seekrit-run` launcher](/docs/guides/run)
- Browse every flag in the [CLI reference](/docs/reference/cli)
