# How to share .env files with your team securely

> Five ways teams share .env files, compared by who can read the values and what happens when someone leaves, plus a setup that keeps env files out of chat.

URL: https://crunu.com/guides/share-env-files-with-a-team
Language: en
Updated: 2026-10-30

Commit a .env.example template, never real values. Keep the values in one encrypted place with a separate environment for each stage, give each person only the environments their work needs, and start programs with the values injected. Use an expiring link only for someone outside the team.

Share the variable names, not the values. Commit a `.env.example` template to Git, keep the real values in one encrypted place with a separate environment for development, staging and production, and let each person open only the environments their work needs with their own key. Programs then get their values when they start, and nobody passes a file around. The rest of this guide compares the five common ways teams share `.env` files, shows which one fits which team, and walks through a setup you can move to in an afternoon.

## What sharing env files actually has to solve

"Send me the `.env`" sounds like one problem. It is four.

1. **Who can read the values.** Not "who should", but who can: the people in the chat channel, the chat provider, everyone with a clone of the repository, the backup service, the laptop that synced the file.
2. **How a change reaches everyone.** Someone rotates the database password on Tuesday. On Wednesday half the team still runs with the old one, and a new hire copies the file from whoever answered first. This is configuration drift.
3. **What happens when someone leaves.** Removing an account is quick. The values that person saw keep working until someone replaces them at the provider, and you can only replace what you know they saw.
4. **Where the copies end up.** Every method creates copies. The question is whether you know where they are and whether you can stop new ones.

The [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html) puts two of these as rules: centralize secrets management, and don't give engineers access to all secrets. Keep both in mind as you read the comparison.

## Five ways teams share .env files

| Method                                      | Who can read the values                                         | How a change reaches everyone         | When someone leaves                                 |
| ------------------------------------------- | --------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------- |
| Paste into chat or email                    | everyone in the conversation, the provider, every synced device | someone pastes again                  | nothing to revoke; every value they saw stays valid |
| Template in Git, values handed over by hand | whoever received the values                                     | by hand, person by person             | you guess what they had                             |
| Shared password manager item                | everyone with access to the vault or item                       | the item updates; local copies don't  | remove them; replace the values yourself            |
| Encrypted `.env` in the repository          | everyone who holds the decryption key                           | commit and pull                       | rotate the key, re-encrypt, reissue the values      |
| Secrets vault with environments             | people, machines and pipelines granted each environment         | the next run picks up the new version | access ends; the vault tells you what to reissue    |

The sections below go through each one, including where it is the right choice.

### Pasting into chat or email

It is instant. It also creates copies you can't see. A message with a `.env` file lives in the chat provider's storage, in search indexes, in notifications on phones, in exports and in the history of everyone who joins the channel later. Editing or deleting the message removes one copy, not the rest.

*Figure: Diagram of where a secret goes once it is in a team chat. Chat provider’s servers: message history and search; it stays after you delete it. Workspace exports and backups: copies you don’t control; it stays after you delete it. Apps and bots in the channel: integrations that read messages. Every member’s devices: desktop and phone apps, notifications; it stays after you delete it. People who join later: they can scroll back. Deleting the copy doesn’t undo the leak, so the value has to be reissued at its provider.*

There is a second, quieter cost: nobody can tell later who received which values. When the person leaves, you can't answer "what do we need to reissue" from a chat channel. [Why .env files in chat are a liability](https://crunu.com/blog/why-env-files-in-chat-are-a-liability) covers how to clean up a channel that already has files in it, and [the comparison page](https://crunu.com/alternatives/env-files-in-chat) sets chat against a vault point by point.

### A template in Git, values handed over separately

This is the baseline every team should have regardless of what else it uses. The repository holds a `.env.example` with every variable name and a safe placeholder, `.env` is listed in `.gitignore`, and the real values travel some other way.

```dotenv
# .env.example: names and safe defaults only
DATABASE_URL=postgres://localhost:5432/payments_dev
STRIPE_SECRET_KEY=
SENTRY_DSN=
LOG_LEVEL=debug
```

The template documents what the program needs, and it makes a missing variable obvious in code review. [The Twelve-Factor App](https://12factor.net/config) gives a useful test for whether config is separated from code: could the codebase be made open source at any moment without exposing a credential? A repository with only a template passes.

The template does not answer the four questions. It only moves them to "some other way". If that other way is chat, you are back to the previous section. Pair the template with one of the methods below, and keep it current: a template that is missing three variables trains people to ask for "the real file" instead.

Never commit the real file, even to a private repository. Git keeps every version forever, and cleanup is harder than it looks. GitHub's guide to [removing sensitive data](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository) says the first step is to revoke or rotate the secret, and warns that rewritten history can still be reachable in clones, forks, pull requests and cached views.

### A shared password manager item

Some teams keep the `.env` contents in a shared item of their password manager. For two or three people who already share a vault, this is a reasonable choice: the values are encrypted, access is per person, and an update to the item is visible to everyone.

It gets harder as the team grows. A password manager thinks in items and vaults, not in environments of a project, so separating staging from production means separate vaults or items and someone keeping them straight. The values still end up in a local `.env` file once someone copies them out, and that file is where tools, backups and screen shares find them.

Some password managers now address the local file. 1Password Environments, for example, can mount a [local .env file](https://www.1password.dev/environments/local-env-file) that is served through a UNIX named pipe and never stored on disk, on Mac and Linux. Its documentation is candid about the trade-offs: once the file is authorized, every process can read it until 1Password locks, and the first process to read it wins if two read at once. If your team already lives in 1Password, read [Crunu vs 1Password](https://crunu.com/alternatives/1password) for where each fits.

### An encrypted .env file in the repository

Tools such as SOPS, dotenvx and git-crypt encrypt the file so it can be committed. The ciphertext travels with the code, every change is a commit, and pull requests show that a value changed.

They differ in where the key lives:

- **dotenvx** writes the public key into `.env` and the private key into `.env.keys`. Production gets the private key as `DOTENV_PRIVATE_KEY` or a per-environment variant such as `DOTENV_PRIVATE_KEY_PRODUCTION`, per the [dotenvx README](https://github.com/dotenvx/dotenvx).
- **SOPS** encrypts with age, PGP or a cloud KMS key, and can use different keys for different files, so production files can have a smaller set of recipients.
- **git-crypt** decrypts on checkout for anyone who holds the key, added through GPG or exported as a symmetric key file.

The weak point is not the encryption. It is the key distribution and the history. Someone still has to hand each new developer a private key, often through the same chat channel the team was trying to leave. And every old commit stays readable with the key that encrypted it.

*Figure: Diagram of where a secret goes once it is in a Git repository. Git host: the repository, pull requests and forks. Commit history: old commits keep the value after the file is deleted; it stays after you delete it. CI caches and artifacts: copies of the checked-out files. Every clone: laptops and build machines that pulled it; it stays after you delete it. Everyone with read access: today and anyone added later. Deleting the copy doesn’t undo the leak, so the value has to be reissued at its provider.*

Removing a person shows the limits most clearly. The [git-crypt README](https://github.com/AGWA/git-crypt) says it does not support revoking access that was previously granted, and that someone with an old key can still read old history. SOPS documents a procedure: remove the key from `.sops.yaml`, run `sops updatekeys`, then `sops rotate --in-place` on each file, and only then rotate the actual passwords and API keys, as its [key management guide](https://getsops.io/docs/usage/key-management/) puts it. That is the right order, and it is manual for every file.

An encrypted file in Git works well for a small team with few environments that rarely changes membership. It also works as a delivery format for infrastructure code. For a team that adds and removes people every quarter, the per-file work adds up.

### A secrets vault with environments

A vault stores values per project and environment, gives each person and machine their own access, and delivers values at run time rather than as a file someone copies. The answers to the four questions are built in: access is per environment, the latest version is what everyone gets, removal ends access, and the vault knows who received what.

Vaults differ in one thing that matters for the comparison above: who can decrypt. Some decrypt on their servers, which lets them sync values to other platforms and generate credentials on demand. Others encrypt only on your devices, so the server stores ciphertext it can't read. Neither is wrong, but you should know which you have. [Who can decrypt your secrets](https://crunu.com/guides/who-can-decrypt-your-secrets) compares 18 products by that question, with a quote from each one's documentation.

### A one-time link, for someone outside the team

Contractors, auditors and the agency that builds your marketing site need a few values for a few days. An invitation is too much, and chat is too permanent. An expiring link to exactly the files they need is the right tool: it can be opened a set number of times, it can need a password, and you can revoke it.

*Figure: Example: Priya creates a single-view link to one Stripe file in payments-api staging for Luis Ortega, a contractor without an account. Luis opens it, sees the owner’s signature verified and a warning that this was the last view, and a second attempt shows “This link no longer works”.*

In Crunu, a [share link](https://crunu.com/docs/concepts/access-links) keeps its decryption key in the part of the address after `#`, which browsers never send to the server. A link can expire after up to 365 days and up to 1,000 views, including a single view, and it can require a password. Only an explicit **Open** uses a view, so a chat app that renders a link preview doesn't burn it. Revoking a link stops future opens. It can't take back what was already opened, which is why the values still get reissued after the work ends. [Sharing a secret with a contractor](https://crunu.com/blog/share-secrets-with-contractors) walks through a full handover.

## Which method fits which team

There is no single answer, and pretending otherwise would be dishonest. Here is a reasonable starting point.

| Team                                 | What works                                                                  | Why                                                                      |
| ------------------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| One developer                        | a template in Git plus a password manager                                   | one person, one machine; the vault questions barely apply                |
| Two or three people, one environment | a template plus a shared password manager item, or an encrypted file in Git | membership rarely changes; key handover is a single conversation         |
| A team with staging and production   | a template plus a vault with an environment per stage                       | per-environment access and a record of who received what start to matter |
| A team with CI and servers           | the same, with machine and pipeline access                                  | values reach servers and pipelines without files being copied around     |
| Anyone outside the team              | an expiring link to specific files                                          | limited in time and views, revocable, and nothing joins your directory   |

If you are unsure, look at the next departure. Ask what you would reissue and how you would know. If the honest answer is "everything, just in case", the method has stopped scaling.

## A setup with one environment per stage

The rest of this guide describes a setup with one encrypted environment per stage. It uses Crunu for the concrete steps, but the shape is the same in any vault: a template in Git, values in environments, access by role, and values loaded at run time.

### One environment per stage, each with its own key

In Crunu a project, such as `payments-api`, holds environments such as `development`, `staging` and `production`. Each environment is the unit of encryption and of access: it has its own key, sealed separately to each person, machine and pipeline allowed to read it. A developer who works on development and staging never receives the production key, so even a mistake on the server couldn't give them production. [One key per environment](https://crunu.com/blog/one-key-per-environment) explains why that boundary is stronger than a permission check alone.

An environment holds files, not only variables. A `.env` file shows up as a table of variables, an INI file such as `~/.aws/credentials` as fields by section, flat YAML as fields, and anything else as text. Each file has a path relative to the home directory, so the same environment lands in the same place on every computer.

### Access by role, not by file

Hand out environments, not files. A person gets a role on each environment:

- **Editor** reads and changes the environment;
- **Reader** reads it;
- **Specific files** reads only the files chosen for them;
- **Operator** enrolls machines and requests access for them, but has no key and sees no values.

Granting roles one by one gets slow as the team grows, so group them. An **access profile** is a named set of "environment → role" pairs, such as a `Developers` profile with Editor on every development and staging environment. Adding someone to the profile grants all of it, and changing the profile changes it for everyone in it. Each version of a profile is signed.

*Figure: Example: Priya Natarajan edits Kiteline’s “Developers” access profile. In the “Add environments…” window the Editor role is selected, and the quick picks “All development” and “All staging” have ticked five environments while the production column stays empty. Behind it, the access profile page shows version 2, signed by Priya, and its two members, Jonas Lindqvist and Tom Halvorsen, who both get the change. The server stores the signed access profile but can’t change it.*

Example. Kiteline is a fictional company. Jonas Lindqvist joins as a developer. Priya, the owner, invites him and adds him to `Developers`. He can open every development and staging environment of `payments-api` and `web-app` on his first day, and production stays closed until someone grants it. See [Roles](https://crunu.com/docs/concepts/roles) and [Access profiles](https://crunu.com/docs/concepts/access-sets).

### Values loaded at run time, not copied into files

The point of the vault is lost if everyone copies the values into a local `.env` on day one. Instead, start the program with the values injected:

```sh
crunu run --env payments-api/development -- npm run dev
```

`crunu run` decrypts the environment in memory and starts the command with its variables. By default the program gets a short allowlist from your shell, such as `PATH` and `HOME`, plus the variables of the environment, and no file is written into the project. When a new version is saved, the next run gets it; with `--restart on-change`, a running program is restarted with the new values. The [crunu run reference](https://crunu.com/docs/cli/run) lists every option.

Some programs read a file from a path, such as `~/.aws/credentials`. For those, `crunu pull` writes the files of an environment with mode 0600, keeps the previous version next to it as `.prev`, and refuses to write into places like `~/.ssh/` or shell startup files. See [crunu pull](https://crunu.com/docs/cli/pull).

Before you run anything from the terminal, sign in once on that computer with your own account. Your keys are derived on the computer, and the password and Secret Key never go to the server:

```sh
crunu login --server https://app.crunu.com --email jonas@kiteline.example
crunu unlock --ttl 8h
```

The details, including the second factor in the terminal, are in [Sign in from the terminal](https://crunu.com/docs/cli/sign-in-from-the-terminal).

## Moving your team over in an afternoon

This is the sequence that keeps the move short and leaves no stray copies behind. It assumes you have a Crunu account; if not, [create one](https://crunu.com/signup), which is free during early access.

1. Create a project for each application, with an environment per stage, such as `development`, `staging` and `production`.
2. Drag your existing `.env` files and folders like `.aws` onto the matching environment. Crunu proposes a path for each file and shows the variable names without values before adding them.
3. Tick **Load as environment variables (crunu run)** on the env files your program should get as variables, and save the version.
4. Commit a `.env.example` with the variable names, and add `.env` to `.gitignore` if it isn't there yet.
5. Create an access profile such as `Developers` with Editor on development and staging, then invite the team and add them to it.
6. When each person accepts, compare the key fingerprint shown on their screen with the one in the app, on a call or in person, before confirming them.
7. Replace `npm run dev` in your README with `crunu run --env payments-api/development -- npm run dev`, and ask everyone to delete their local `.env` copies.
8. List the values that were ever pasted into chat or committed, and reissue them at their providers. Save the new values in the vault, not in the old channel.

Step 8 is easy to skip. Moving into a vault protects every version from now on. It does nothing for the copy of last year's Stripe key in a channel export.

*Figure: Priya Natarajan dragged the .aws folder from her computer onto the payments-api production environment. Crunu recognized two files and proposes where each one goes: \~/.aws/config, marked as able to run commands, and \~/.aws/credentials. Only field names such as aws_access_key_id are listed; values are not shown. The cursor rests on Add (2).*

The fingerprint check in step 6 is not ceremony. An invitation link carries a secret that the server never sees, and comparing fingerprints confirms that the key you are about to trust really belongs to the person you invited. [Invite your team](https://crunu.com/docs/getting-started/invite-your-team) shows both screens.

## Keeping environments in sync

Once values live in one place, drift becomes visible instead of mysterious. Three habits keep it that way.

**Compare before you promote.** Drift can be a variable that exists in development but was never added to production, or a value that differs between staging and production for no reason anyone remembers. In Crunu, comparing two environments lists the files and variables that exist in only one, and the ones that differ, with values hidden until you reveal them. You can copy selected values across as a new version. See [Compare and promote environments](https://crunu.com/docs/guides/compare-and-promote-environments).

**Treat the template as part of the change.** A pull request that adds a variable should add it to `.env.example` in the same commit. Reviewers then see the new name, and the vault environment gets the value before the merge, not after a failed deploy.

**Use history instead of backups of files.** Every save is a signed, encrypted version, and the latest 100 are kept. When a change breaks something, look at what changed by name and restore the previous version as a new one, rather than asking who still has the old file.

## When someone leaves

Removing a person from a vault cuts off new access, but it can't erase what they already saw. The full procedure, including the order of steps and how to reissue values at providers without downtime, is in the [developer offboarding guide](https://crunu.com/guides/developer-offboarding-secret-rotation). Three points belong here because they depend on how you share in the first place:

- If values were shared through chat, you have to assume the person saw all of them, and reissue all of them.
- If you used an encrypted file in Git, they still hold a key that opens every old commit, so the file's key has to change and the values have to be reissued too.
- If access was per environment in a vault that records deliveries, you can reissue only what they actually received. Crunu's removal wizard shows which versions of which environments the person received, rotates the keys of those environments, and lists the values to reissue, by name and never by value.

## What a vault does not fix

It is worth being clear about the limits, because they are where real incidents come from.

- **The running program sees its values.** Variables injected at start are in the program's process environment, and so are visible to the program and to anything it starts. Keep the environment small: a test job doesn't need production.
- **Values that reach the browser are public.** A variable that a framework inlines into the client bundle, such as one with a `NEXT_PUBLIC_` prefix, is visible to every visitor. No vault makes it secret again.
- **Anyone who can read a value can copy it.** Hiding values in an interface prevents accidents on a shared screen. It does not stop the person who has the key.
- **The vault's server still sees metadata.** Even when values are encrypted on your devices, the server knows project and environment names, email addresses and the audit log. Don't make an environment name a secret in itself.
- **Values outside the vault are outside its record.** A value someone copied into a note, a terminal history or an AI assistant's prompt isn't in any delivery log.

## A short checklist

- [ ] `.env.example` in Git with every variable name, `.env` in `.gitignore`
- [ ] One environment per stage, each with its own key
- [ ] Access by role through access profiles, with production granted separately
- [ ] Programs started with `crunu run` or a similar run-time injector, not a copied `.env`
- [ ] Expiring links, not invitations or chat, for people outside the team
- [ ] Every value that was ever pasted into chat or committed has been reissued
- [ ] A known procedure for removing someone, rehearsed once

For more detail, see the [vault feature overview](https://crunu.com/features/vault), [Files and variables](https://crunu.com/docs/concepts/files-and-variables), and, if your team also uses AI coding tools, [keeping secrets away from AI coding agents](https://crunu.com/guides/secrets-and-ai-coding-agents).

## FAQ

### What is the safest way to share a .env file with a teammate?

Don't send the file. Put the values in an environment that both of you can open with your own keys, and send nothing but an invitation. The teammate gets the current version, every later change, and loses access when they are removed. A file sent through chat stays in the chat history and in every device that synced it.

### Should we commit our .env file to Git if the repository is private?

No. A private repository is still copied to every clone, fork, CI cache and backup, and Git history keeps every value you ever committed. Commit a .env.example with variable names and safe placeholders, and add .env to .gitignore.

### Is an encrypted .env file in the repository good enough?

It can be, for a small and stable team. Tools such as SOPS, dotenvx and git-crypt encrypt the values, but someone still has to hand out the decryption key, and old commits stay readable with the old key. Removing a person means rotating the key and reissuing the values they could read.

### How do we share a secret with a contractor who has no account?

Use an expiring link to only the files they need, with a view limit and a password sent through a different channel. When the work ends, revoke the link and reissue the values at their providers, because whatever they opened is already on their device.

### Can a developer on the team read production values?

Only if someone grants them production. In Crunu every environment has its own key, so a developer who works on development and staging never receives the production key at all, and the server can't hand it to them.

### Does Crunu keep .env files out of AI tools and build logs?

Partly. crunu run passes values to the program it starts without writing a file into your project, so file-reading tools find nothing to read. But the program, and anything it starts, can still read its own environment variables.

## Sources

- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html) (accessed 2026-10-30)
- [The Twelve-Factor App: Config](https://12factor.net/config) (accessed 2026-10-30)
- [Removing sensitive data from a repository, GitHub Docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository) (accessed 2026-10-30)
- [git-crypt README](https://github.com/AGWA/git-crypt) (accessed 2026-10-30)
- [SOPS: key management](https://getsops.io/docs/usage/key-management/) (accessed 2026-10-30)
- [dotenvx README](https://github.com/dotenvx/dotenvx) (accessed 2026-10-30)
- [1Password Developer: local .env files](https://www.1password.dev/environments/local-env-file) (accessed 2026-10-30)
