Files
KNELSecretsManager/docs/ADR-002-ContainerBW.md
T
VP TechOps 657939d182 feat: container-based Bitwarden CLI, no host Node.js
Adds the dockerized bw deployment in production use on the TSGCOO
orchestration host since 2026-08-13: pinned debian-slim image carrying
the pre-compiled bw binary, an in-container auth lifecycle entrypoint
(config, API-key login, unlock, sync), a transparent host wrapper, and
a one-command installer.

ADR-002 records the decision and supersedes ADR-001 for BW CLI
purposes: hosts keep zero language runtimes. Known caveat documented:
the upstream "native" binary is a Node.js SEA, so Node is embedded in
the image though absent from all hosts.

Shellcheck clean (zero warnings incl. info-level).
2026-08-14 09:49:23 -05:00

3.2 KiB

ADR-002: Container-Based Bitwarden CLI (No Host Node.js)

Status

Accepted (supersedes ADR-001 for BW CLI purposes)

Context

ADR-001 selected a hybrid Node.js version-management strategy because the Bitwarden CLI (bw) is distributed via npm and therefore requires a Node.js runtime on every consuming host. For TSYS hosts operating under CMMC L3 / ITAR / STIG alignment, any host-side language runtime is an attack surface and a compliance finding. The KNEL/TSYS baseline ("Docker for everything") also forbids host language toolchains.

Bitwarden additionally publishes the CLI as a pre-compiled single binary. Distributing that binary inside a minimal pinned container gives every host transparent bw access with zero host-side runtimes.

Decision

Run the official pre-compiled bw binary inside a pinned Docker container, exposed to users as a transparent bw wrapper at ~/.local/bin/bw.

Components (all in this repo):

File Role
docker/bw-native/Dockerfile debian:12-slim + ca-certificates + bw binary, pinned 2026.7.0
bin/bw-entrypoint.sh In-container auth lifecycle: config server, API-key login, unlock, sync, exec
bin/bw-cli.sh Host wrapper: docker run with volume-persisted session, tsys- container prefix
bin/bw-install.sh One-command installer for the wrapper + image

Credentials live only in ~/.config/bw/env (single-quoted values, mode 600) per the org-wide "no secrets on disk except BW access info" rule. The Vaultwarden API key is used for login (no TOTP interaction needed); the master password unlocks the vault; bw sync runs after every login so multi-client vault state stays coherent.

Known Caveat

The Bitwarden "native" Linux binary is actually a Node.js SEA (Single Executable Application): it embeds a Node.js runtime and can print Node errors on crash. Node.js is absent from every host, which satisfies the host-hygiene goal, but the "zero Node.js anywhere" stretch goal is not met. Alternatives (Rust vaultwarden clients such as rbw) were considered and rejected for now due to Vaultwarden API compatibility gaps. Revisit if a mature Rust client emerges; the wrapper isolates users from this swap.

Consequences

Positive

  • Zero language runtimes on hosts (CMMC/ITAR/STIG-friendly host hygiene)
  • Identical bw behavior across all TSYS hosts; version pinned in one Dockerfile
  • Session persistence via named Docker volume; no state divergence when wrapper is used consistently
  • Vendoring into shell frameworks is one installer invocation

Negative

  • Requires Docker on every consuming host (accepted: it is an org baseline)
  • SEA caveat above (Node embedded inside the container image)

Verification

Deployed and verified on the TSGCOO orchestration host against https://pwvault.turnsys.com (account coo@turnsys.com): status, list, generate, get password/totp/item, create/edit items. Shellcheck clean at zero warnings including info-level. In production use since 2026-08-13.


Decision Date: 2026-08-14 Decision Makers: VP TechOps (proposed), TSGCOO (implemented) Related: ADR-001 (superseded for BW CLI purposes; MISE guidance remains valid for projects that genuinely require host Node.js)