Back to Blog

Why Can't We Commit Our .env Files?

8/26/2026

At a previous job I was in charge of mentoring the new hires. Which meant that every time somebody started, at some point on their first day, I had to get them our .env file.

Sometimes they didn't have access to the password manager yet. So we'd come up with a less bad way to send it. Something that wouldn't get us in trouble.

That job wasn't unusual. Over the years I've moved a production secret by committing it, by emailing it, by pasting it into a Slack DM, and by putting it in a password manager, which is the one that's supposed to be fine. And once, on a sticky note.

None of them felt right.

I didn't examine any of that for years. It was just a thing you did on somebody's first day, like getting them a badge.

Then a while later I watched a video where Theo ran through a list of things he wished someone would go build, because he didn't have the time himself. One of them he called better git. And somewhere in the middle he said it was dumb that we can't commit our .env files, because we don't trust git to share them properly.

I'd used git every working day for years. I'd never once thought about it.


Three Things I Trust

So I started thinking about it. And the part I couldn't get past is that I trust git with every file I write. I trust GitHub to keep all of it on a machine I have never seen and will never touch. And I trust 1Password with the credentials to my company's entire infrastructure.

Three things I trust completely. And the .env is allowed to live in exactly one of them.

On the face of it they're the same shape. Both are shared with everyone on my team. Both sit behind a login. And both of them live on somebody else's servers. If I'm honest about what I can actually see from the outside, I couldn't tell you what makes one of them the right place for a secret and the other one the wrong one.

The answer I reached for first was intent. GitHub is for code, 1Password is for secrets, and you don't put things where they don't belong. It's about what a tool is for, and about trusting it to do that job and not some other one.

That's the answer I had, and I was happy with it.


What's Actually Different

The actual difference has nothing to do with what the tools are for. It's about who can read what's inside them.

GitHub's own docs will tell you that private repositories are "indexed and searchable by those that already have access to those private repositories."1 That index doesn't build itself. Something over there reads every line of your private code, because reading it is the only way search can work.

1Password's security page says the opposite about itself, and says it without hedging. Their dual-key model "ensures that even we can't see exactly what you've saved in 1Password."2

Which means the thing I'd been calling a gut feeling was never really a gut feeling. I'd been reading the difference correctly for years. I just couldn't say what it was I was reading.

It also puts the sticky note in a strange light. Every clever less bad way I came up with was a plaintext channel that somebody else's servers read on the way past. The email. The Slack DM. The commit.

The piece of paper on my desk was the only one that never phoned home.

It was also completely useless. It doesn't reach a new hire in another city. It doesn't reach CI. It falls behind the desk.

The most private thing on the list was the least useful thing on the list, and it took me a long time to notice those two facts had anything to do with each other.


The Other Half

So there are two things going on, and I'd been treating them as one.

One is who can read it. The other is where it actually lives. GitHub gives you the second and not the first. 1Password gives you the first and not the second. Email and Slack and the commit give you neither. And the sticky note gives you the first so completely that nothing else can reach it at all.

Nothing gives you both.

But I should be fair to the tools I've been complaining about. Nobody is ignoring this. GitHub's push protection is on by default for your account, and it will stop you pushing a secret to a public repo.3

That's a real answer. It's just the opposite one. It works by keeping the secret out, and what I wanted was a way to let it in.

And once you can see the shape of the thing that's missing, it's hard to stop listing what it would fix. The .env travelling with the repo that needs it. CI just having access, instead of a second copy pasted into a settings page. Being able to look back and see what a value was last month, and when it changed.

I wanted the loop to close. Not another system for secrets, just the secrets sitting where they already belonged.

Which is roughly the point where a reasonable person moves on with their life.

I started writing a version control system instead.


What I Built

It's called loot. The idea is one sentence long. Individual files get locked, inside an otherwise normal repo, and whatever is hosting that repo never gets a key.

Though locked is the wrong word and I should stop using it. In version control, locking already means the Perforce thing, where you take a file so nobody else can edit it. That's about writing. This is about reading. loot calls it sealing, which is the better word anyway, because a seal is something you can see is there without being able to open it.

Two files and one command.

$ printf 'TOKEN=supersecret\n' > .env
$ printf '.env restricted=alice\n' > .lootattributes
$ loot init --identity alice
$ loot describe -m "initial work"

The .env is committed now, and I can still open it and read it like any other file in the repo. So here's the only question that actually matters.

$ grep -r supersecret .loot/
$

Nothing. The file is tracked, I can read it, and the bytes that would travel to a host don't contain it.

What it doesn't do is keep a secret safe from the people you gave it to. If I seal the .env to my team then my team can read it, and any one of them can send it to somebody exactly like before.

That isn't an oversight I'm hoping you won't notice. It's written into loot's own docs as an explicit non-goal, on the grounds that it's unachievable and that a control implying otherwise would be worse than none.

Sealing decides who gets a key. It has nothing to say about what anyone does after they turn one.

And I keep coming back to the new hires. My whole job with somebody on their first day was showing them how we do things here. The first thing I ever showed them was how to move a secret in a way that wouldn't get us in trouble.

Which is not the same as a way that was safe. I knew that at the time. I just didn't have anywhere better to put it.

Theo's whole video was a list of things he wished somebody would go and build, because he didn't have the time.

I didn't really have the time either. It's called loot, it is nowhere near finished, and if you have ever pasted a .env into a Slack DM and felt slightly bad about it, you might like where it's going.

Thanks for reading. Still learning out loud over here.