DEV Community

Cover image for A new Git worktree shouldn’t mean another copy of your secrets
Titan Ron
Titan Ron

Posted on AI-assisted

A new Git worktree shouldn’t mean another copy of your secrets

Git worktrees let you keep several branches checked out at once, each in its own directory. Getting your application running in each directory can still involve some manual work.

You create a worktree, install dependencies, and start the app. It fails because the ignored .env file stayed behind in the other checkout.

You can copy the file, create a symlink, or run a script to download the values. Each approach can work, but it adds something to maintain. Copying plaintext secrets also creates more files to keep current when those values change.

I’m building resenv to simplify this part of the workflow: keep references in project configuration and resolve their values when the application starts.

For Node and Nx projects using Doppler, a reference looks like this:

API_TOKEN=doppler://my-project/dev/API_TOKEN
Enter fullscreen mode Exit fullscreen mode

It identifies the project, configuration, and secret to retrieve. The reference itself grants no access. The authenticated developer still needs permission to read that secret.

At startup, resenv fetches the value and replaces the reference in the application’s environment before application imports execute. Your code continues to read process.env.API_TOKEN.

Make the references available in every worktree

Putting references in an ignored .env file removes plaintext values from that file, but the file would still be missing from a new worktree.

To remove that setup step, the reference configuration needs to travel with the project.

Here’s an example using an Nx target. It assumes the application’s entry point is app.mjs inside its project directory.

Install resenv:

npm install --save-exact @ciqol/resenv
Enter fullscreen mode Exit fullscreen mode

Configure authentication using the Doppler setup guide. Supported methods include an explicit token, resenv’s OS credential storage, and existing Doppler CLI credentials. The guide covers platform support and current login limitations.

Then configure the application target:

{
  "name": "api",
  "targets": {
    "serve": {
      "executor": "nx:run-commands",
      "cache": false,
      "continuous": true,
      "options": {
        "cwd": "{projectRoot}",
        "command": "node --import @ciqol/resenv/resolve app.mjs",
        "env": {
          "API_TOKEN": "doppler://my-project/dev/API_TOKEN"
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This target contains a reference rather than a credential. Project and secret names can still reveal configuration details, so review whether those names are appropriate to include in your repository.

Run the target:

npx --no-install nx serve api
Enter fullscreen mode Exit fullscreen mode

Nx supplies the environment to the application process. The resenv preload resolves the reference before the application starts.

With this configuration available alongside the code, another worktree receives the same reference and startup command. You still install dependencies and complete the application’s usual setup, but you don’t need another plaintext copy of API_TOKEN.

Keep your existing environment loading

resenv resolves values already present in the environment. Your existing loader remains responsible for selecting and loading configuration files.

In the example above, the reference is explicitly supplied through the target’s env option, which Nx gives precedence over dotenv files. If you prefer file-based overrides, references can live in the files your loader selects instead. Those files still need to be provisioned in each worktree.

The preload belongs on the application process, after its environment has been loaded. Preloading the outer Nx process would run too early.

Each reference explicitly selects its Doppler project and configuration. Worktrees can use the same references or different ones where needed; resenv doesn’t choose a configuration from the branch name.

Resolution happens once at startup. Restart the application to fetch updated values. If a required lookup fails, startup stops before resenv applies replacements.

Where this fits among the alternatives

Doppler CLI already supports injecting secrets for a selected project and configuration. 1Password CLI supports resolving secret references at runtime too.

resenv focuses on resolving individual Doppler references within the environment your Node application already receives. That includes Nx’s existing loading flow and references pointing to different Doppler projects or configurations.

The benefit I’m trying to validate is less setup when moving between worktrees, while keeping the application’s environment-loading conventions.

A specific security benefit

resenv doesn’t write fetched values back into dotenv files or cache those values on disk. This reduces the need to maintain plaintext secret files in working directories.

Your application still receives the resolved secrets. resenv doesn’t isolate worktrees or programs running with access to the same credentials. Doppler permissions remain the authorization boundary.

resenv is available as an experimental beta. It currently supports Doppler as its only built-in cloud provider, requires Node.js 20 or later, and has no runtime npm dependencies.

I’m looking for feedback from developers working across multiple Git worktrees:

How do you make secrets available in a fresh worktree today, and which part of that setup would you most like to remove?

Source and setup instructions · Share feedback

Top comments (2)

Collapse
 
theoephraim profile image
Theo Ephraim •

just... use varlock

Collapse
 
titanron profile image
Titan Ron •

Thanks for bringing up Varlock, Theo!

I was actually aware of it and tried it before building resenv. There's definitely some overlap, but it didn't quite solve the specific problem I was facing.

What I wanted was to preserve Nx's existing environment-loading behavior, including file selection and overrides, and simply resolve secret references already present in process.env before application code runs.

This way, existing project configuration can travel with each Git worktree, without copying secrets or introducing a separate configuration-loading layer.

So while Varlock offers a broader configuration solution, resenv intentionally focuses on this narrower use case.

Appreciate the suggestion!