Back to Our Work

April 2026

LAZYENV: Sync .env Files, Not a Secrets Vault

A small Python CLI and TUI that syncs .env files to a private Firestore collection, keyed by the folder name. The smallest mechanism that fixes lost .env files.

Developer Tools · CLI & TUI

Most developers we know have at least one project where the .env file is missing on at least one of their machines. It never copied across when the laptop was replaced. A teammate has a slightly different version. The only complete copy is buried in a Slack thread that scrolling cannot find anymore.

The .env file is the small, untracked file that holds the secrets a project cannot run without: a DATABASE_URL, a Stripe key, an OAuth client ID, an LLM provider token. It cannot go in git, it does not follow you to a new machine, and it tends to be shared through whichever channel is at hand: a paste in chat, an email attachment, a screenshot, a Notes app entry. None of these are version control.

lazyenv is a small command-line tool we built to remove that ritual. Inside any project folder, you run lazyenv, and either push the local .env up to a private cloud copy or pull the cloud copy down. It is deliberately not a secrets vault, and it is not trying to be one.

lazyenv TUI showing a single project card. A gradient ASCII LAZYENV banner sits at the top. Below it, a card lists the project name (test), the local folder path, the local and remote .env state both at 2 vars, and a green IN SYNC status. The local .env contents are shown beneath the card. A footer along the bottom shows the single-key bindings: q quit, l login, e edit, p push, g get, r refresh, D delete remote.

What it does

Inside any project folder, the TUI shows one card. The project name is the basename of the directory you are in. The card shows the state of your local .env, the state of the remote copy, and a colored status indicator. Green means local and remote match, yellow means they have diverged, cyan means the remote exists but you have nothing locally.

The footer is single-key bindings: p pushes the local file up, g pulls the remote down, e opens a built-in editor with line numbers and syntax highlighting, r refreshes, D deletes the remote copy, u self-updates, q quits. In the editor, Ctrl+S saves the file and pushes it to the cloud in one motion.

Every TUI action is also a CLI subcommand: lazyenv push, lazyenv pull, lazyenv edit, lazyenv list. The TUI is for the interactive case. The CLI is for shell scripts and CI pipelines.

The design choices that keep it small

Most developer tools fail by trying to be more than the problem requires. The .env sharing problem is small. We wanted a tool whose mechanism is small enough to keep in mind in a single sitting. Four decisions did most of the work.

1

The folder name is the key

When you cd into ~/repos/xobot and run lazyenv, the tool reads the basename of your current directory and looks up a Firestore document at envs/xobot. There is no init step, no project list to maintain, no flag to remember. The same way git knows which repo you are in by walking up to the nearest .git, lazyenv knows which .env you are talking about by looking at the folder you are standing in.

The useful consequence: two machines that both clone a repo into a folder named xobot see the same record without any configuration. The deliberate constraint: if you rename the folder, you have a new project as far as lazyenv is concerned. That tradeoff is fine for a tool whose only job is to keep .env files in sync across machines you control.

2

Application Default Credentials, not API keys

lazyenv login is a thin wrapper over gcloud auth application-default login. The same identity that runs your gcloud commands is the one lazyenv uses to read and write Firestore. There are no service accounts to provision and no per-tool API keys to rotate. Access is gated entirely by IAM on the GCP project that holds the Firestore collection.

The reason for this is mostly defensive. Every credential lazyenv could have minted itself becomes a credential we would later need to revoke, audit, or explain to a security team. Piggybacking on ADC means lazyenv inherits the user's existing Google identity and the access controls that already apply to it. If you can read the project, you can use lazyenv. If you cannot, no separate gate to fail open or closed.

3

Firestore as the store, on purpose

A document at envs/{folder-name} holds three fields: the .env content, the machine that pushed it last, and the timestamp of that push. That is the entire data model.

Firestore is not the most exciting choice. It is the right one for this scope. The collection lives in a Google Cloud project the user already owns, the read and write surface is one document at a time, the per-document size limit sits well above any realistic .env, and there is no server to run. The cost at the volume of "a small team's .env files" is effectively zero. A more sophisticated backend would have given us more capability and more things to maintain. The point of lazyenv is to be a small piece of infrastructure that does not need attention.

4

TUI and CLI parity

The TUI is built with Textual. It is the surface most users will reach for, because the single-folder card model lines up with the way the question is usually asked: "what is the state of this project's .env?" The TUI answers that with one screen and one keystroke per action. The built-in editor uses a Textual TextArea with syntax highlighting, so there is no vim or emacs muscle memory required to fix a typo.

Every TUI action also exists as a plain CLI subcommand. The moments you are most likely to need an .env file are the moments you do not want to be in a TUI: setting up a fresh environment from a script, restoring an .env in CI, debugging a deploy at three in the morning. Keeping the CLI at parity with the interactive surface is what lets the same tool fit cleanly into both shells and pipelines.

What lazyenv is not

There is a real category of products that solve secrets management as an organizational concern: HashiCorp Vault, Doppler, Infisical, AWS Secrets Manager, GCP Secret Manager. They have policies, rotation, scoped tokens, and audit trails. They are the right answer when secrets need to be governed, not just shared.

lazyenv is none of those. It has no policies, does not rotate secrets, and does not scope access below the GCP project level. The .env content is stored as a single string field, with the protections Firestore gives any document in the project: encrypted at rest, transport over TLS, IAM on the project as the only access gate.

The honest framing: lazyenv is a developer convenience for individuals and small teams who already trust each other with their .env files and who already share a GCP project. It is the smallest mechanism we could think of that actually makes "I lost my .env" stop being a recurring problem. If you need anything stronger, the right move is one of the tools above, not a more elaborate version of lazyenv.

Why we built it this small

The .env sharing problem is small. The cost of a full secrets vault is large. Most teams we have seen end up paying neither cost and quietly absorb the friction of pasting .env files into chat instead. lazyenv exists because we wanted to stop doing that, and the smallest mechanism we could find that actually works is a folder name, a Firestore document, and an existing Google identity.

Nothing else. We use it daily.