coiai Logo

This article is an English translation of the original Japanese post. Read the Japanese original →

Ditching .env Files for Bitwarden Secrets Manager

July 10, 2026

Categories: Windows, work, コラム, 自動化 · Tags: セキュリティ, プログラミング

Ditching .env Files for Bitwarden Secrets Manager

What's this post about?

At coiai, our main line of work is building mockups, and up until now our dev team has been just one or two people. But we've started taking on projects with six or more developers, which forced us to rethink our development environment.

Up to now, we'd been managing environment variables in .env files. Going forward, though, we wanted to make sure secrets could never be read as plain text, and that sharing environment variables across the team happened securely — so we overhauled our setup with those two goals in mind.

Why we adopted Bitwarden

Background: what's wrong with .env?

Pairing .env with .gitignore is the standard way to handle local dev secrets, but it has a few weak points.

  1. Distribution is manual — you end up handing .env over via chat or a USB stick to new team members or new machines.
    The moment you do, another copy exists, and rotating a secret means redistributing it to everyone all over again
  2. It sits on disk as plain text — at risk of ending up in backups, file shares, or an accidental commit
  3. AI coding agents can read it — an agent with shell or file access, like Claude Code
    or Cursor, will run cat .env or printenv in the course of debugging something, with no
    malicious intent required. Bitwarden itself has called this out in a blog post
    (Your coding agent can read your .env file)

A secrets management service solves problems 1 and 2, and Bitwarden Secrets Manager stands out
for being cheap (it's a separate contract from Password Manager) and easy to set up, since the CLI ships as a single binary.

The core concepts to know in Bitwarden Secrets Manager

One important note: this is a completely separate product from Password Manager (personal password storage)! We already use Password Manager at coiai — this time we added a separate contract for Secrets Manager.

There are only three concepts you need to know.

ConceptRole
ProjectA container for secrets. In our case, one repo = one project
SecretA KEY / VALUE / note triple. Each line in .env becomes one secret
Machine accountAn account for programs, not people. You grant read or read-write permissions per project, which issues an access token
Secrets Manager プロジェクト「MyProject」
  ├─ GOOGLE_CLIENT_ID     = xxxx.apps.googleusercontent.com
  ├─ GOOGLE_CLIENT_SECRET = GOCSPX-xxxx
  ├─ POSTGRES_PASSWORD    = xxxx
  └─ ...
        ▲ 読み書き: 管理者用マシンアカウント(自分)
        ▲ 読み取りのみ: メンバー用マシンアカウント(チーム配布)

Setup steps (Windows)

1. On the Web Vault side

  1. Create a project in Secrets Manager (e.g., MyProject)
  2. Create a machine account → add the project under the "Projects" tab, and
    set the permission to "Can read, can write" (for the account you'll hand out to team members, use a separate account set to "Can read" only)
  3. Issue a token under the "Access Tokens" tab (it looks like 0.xxxxxxxx...)

2. Installing the bws CLI

bws is a single Rust binary. It's not on winget, so grab it from the
GitHub releases page —
bws-x86_64-pc-windows-msvc-<version>.zip — extract it somewhere like
%LOCALAPPDATA%\Programs\bws, and add that folder to your PATH.

$dir = "$env:LOCALAPPDATA\Programs\bws"
New-Item -ItemType Directory -Force $dir | Out-Null
Invoke-WebRequest "https://github.com/bitwarden/sdk-sm/releases/download/bws-v2.1.0/bws-x86_64-pc-windows-msvc-2.1.0.zip" -OutFile "$env:TEMP\bws.zip"
Expand-Archive "$env:TEMP\bws.zip" $dir -Force
# PATH への追加(ユーザー環境変数)
[Environment]::SetEnvironmentVariable("Path",
  [Environment]::GetEnvironmentVariable("Path","User") + ";$dir", "User")

3. Storing the access token

Don't write the token to a file — store it as a user environment variable instead.

setx BWS_ACCESS_TOKEN "0.xxxxxxxx-xxxx-...."

If you're on an EU server contract, you'll also need to set the server URL:
bws config server-base https://vault.bitwarden.eu

4. Bulk-registering an existing .env

Just repeat bws secret create KEY VALUE <project-id> for each entry.
If you want to bulk-register straight from an existing .env, a one-liner like this will do
(we ended up turning this into a helper script that supports push/pull/diff):

$pid_ = (bws project list | ConvertFrom-Json)[0].id
Get-Content .env | Where-Object { $_ -match "^\s*[^#].*=" } | ForEach-Object {
  $k, $v = $_ -split "=", 2
  bws secret create $k.Trim() $v.Trim() $pid_
}

Once you're done registering everything, delete the .env file. That's the whole point of this migration.

Day-to-day usage: bws run

Just prefix any command that needs secrets with bws run --. Every secret in the project gets
injected as an environment variable, keyed by its name, into that process (and its children) only.

bws run -- py scripts/drive_sync.py       # Google API を使うスクリプト
bws run -- docker compose up -d db        # compose の ${VAR} 展開にも効く

Check or change values either from the Web Vault GUI or the CLI:

bws secret list <プロジェクトID>          # 一覧(値も表示されるので注意)
bws secret edit --value "新しい値" <シークレットID>

When you can migrate an existing project without touching the code

bws run only injects environment variables, so as long as your app already reads from the environment, it just works.

Gotchas (the ones I actually hit)

An environment variable set with setx doesn't show up

setx only takes effect in shells opened after you set it. If you run something in the same window
right after and it says the variable's unset, either reopen your terminal or load it manually into the current shell:

$env:BWS_ACCESS_TOKEN = [Environment]::GetEnvironmentVariable("BWS_ACCESS_TOKEN","User")

Getting 404 Not Found on writes

When bws secret create fails with [404 Not Found] Resource not found., it's usually not
that the secret or project is missing — it's that the machine account lacks write permission
most of the time. Bitwarden returns 404 for permission errors too, to avoid revealing whether the resource exists.
If bws project list works but create returns 404, suspect permissions first.

Quoting issues with bws run on Windows

Because bws run re-runs whatever command it's given through a shell, complex quoting like
bws run -- py -c "import os; ..." tends to get mangled partway through and break.
Putting the code in a script file and running bws run -- py script.py instead is far more reliable than a one-liner.

Understand the security limits too

What this setup eliminates is plain-text secret copies sitting on disk, and distribution
depending on one person remembering to do it. That said, some things remain:

The official best practices also recommend going further: split machine accounts
by use case (person, agent, CI), and set expiration dates on tokens meant for short-lived use.

Wrap-up

References

Ditching .env Files for Bitwarden Secrets Manager | coiai Inc.