.env files
4/25/2025
Managing Secrets Without the Pain
Secure storage of secrets — and the proper use of this “sensitive” data in environment variables — has been something on my mind for quite a while.
Over time I tried several approaches. Some worked… until they didn’t.
This post is about how I used to manage secrets, the problems that came with it, and how I recently simplified the whole process.
The Old Approach: Local Encryption
For years my go-to solution was local encryption.
I relied on good old openssl, which is available on almost any system. The idea was straightforward:
- Keep a keyfile somewhere safe (in my case, usually a hidden Google Doc).
- Encrypt
.envfiles using that key. - Store the encrypted files in the repository.
The resulting files would look something like this:
.env.dev.enc
.env.stage.enc
.env.prod.enc
When needed, I would decrypt them locally, update variables, and encrypt them again.
Simple enough — at least in theory.
The Problem With This Workflow
In practice, things became painful pretty quickly.
Every small change required going through the full cycle:
decrypt → edit → encrypt
And this had to be repeated for every environment.
After doing this enough times, it became tempting to take shortcuts.
More often than I’d like to admit, I ended up using a single shared .env file across multiple applications just to make life easier (though who was I really fooling?).
The consequences were predictable.
Sometimes backend environment variables would end up sitting right next to frontend ones — including things like database connection credentials 🤪.
Not exactly ideal.
Another Issue: Separate Secret Storage
There was also a structural problem.
Encrypted .env files usually lived in a separate repository, which meant:
- secrets were updated independently
- application changes and secret changes were not synchronized
- mistakes became easier to make
Any extra moving part in a deployment process increases the chance that something goes wrong.
And this definitely added a few more.
Discovering Doppler
Recently I came across Doppler. It turned out to be a really nice way to manage secrets.
Instead of juggling encrypted .env files, secrets are stored and managed through a clean web interface, and injected directly when running commands.
For example:
doppler run -- npm run build
The application simply runs with the correct configuration profile.
No .env files required.
Even better, Doppler provides integrations with most modern deployment tools.
Why I Still Keep .env Files
That said, I don’t love being completely dependent on a single tool.
If the tool disappears tomorrow, I still want my projects to work.
So instead of abandoning .env files entirely, I decided to generate them automatically using Doppler.
This keeps things portable while still benefiting from centralized secret management.
Automating Setup With Taskfile
To make things smoother, I created a small Taskfile for initializing projects with the correct environment variables:
# yaml-language-server: $schema=https://taskfile.dev/schema.json
version: "3"
tasks:
install:
cmds:
- curl -Ls --tlsv1.3 --proto "=https" https://cli.doppler.com/install.sh | sh
login:
cmds:
- doppler login
get-env:
vars:
DOPPLER_PROJECT: '{{default "help" .DOPPLER_PROJECT}}'
DOPPLER_CONFIG: '{{default "dev" .DOPPLER_CONFIG}}'
FILEPATH: '{{default "" .FILEPATH}}'
cmds:
- doppler secrets download --project {{.DOPPLER_PROJECT}} --config {{.DOPPLER_CONFIG}} --no-file --format env > {{.FILEPATH}}
setup-infra-secrets:
cmds:
- task: get-env
vars: { DOPPLER_PROJECT: "infra", DOPPLER_CONFIG: "{{.DOPPLER_CONFIG}}", FILEPATH: ".env" }
setup-frontend-secrets:
cmds:
- task: get-env
vars: { DOPPLER_PROJECT: "frontend", DOPPLER_CONFIG: "{{.DOPPLER_CONFIG}}", FILEPATH: "apps/frontend/.env" }
setup-backend-secrets:
cmds:
- task: get-env
vars: { DOPPLER_PROJECT: "backend", DOPPLER_CONFIG: "{{.DOPPLER_CONFIG}}", FILEPATH: "apps/backend/.env" }
setup-secrets:
deps: [setup-frontend-secrets, setup-infra-secrets, setup-backend-secrets]
initial-setup:
cmds:
- task: install
- task: login
- task: setup-secrets
With it, generating the .env file becomes part of the normal project setup.
No manual secret handling required.
Final Thoughts
Managing secrets is one of those things that seems simple until it isn't.
My old setup worked for a while, but it created too much friction and too many opportunities for mistakes.
Using Doppler — while still keeping .env files as a fallback — turned out to be a good balance between convenience and control.
And honestly, anything that removes a few steps from secret management is already a win.