A drop-in terraform that picks the right Terraform version for every project — automatically.
Switching Terraform versions between projects is annoying. tfenv install, tfswitch, asdf shims — they all need maintenance, and you still forget to run them. terraform-demux reads each project's required_version constraint, downloads the matching Terraform release, verifies its SHA-256 against HashiCorp's published SHA256SUMS, caches it, and execs it — so a single terraform command Just Works in every project.
No per-version installs. No shims. No surprises.
- Zero-config — reads
required_versionfrom yourterraform { ... }block, walking parent directories until it finds one - Drop-in — installs a
terraformsymlink, so existing scripts, CI, and IDE plugins keep working - Verified downloads — every archive is checked against HashiCorp's published
SHA256SUMSbefore it's cached or executed - Concurrency-safe cache — per-binary
flockkeeps parallel invocations from re-downloading the same release - Apple Silicon friendly —
TF_DEMUX_ARCH=amd64lets you run older Terraform releases that have noarm64build - Safer state operations — refuses
terraform import,state mv, andstate rmon Terraform versions that have config-block alternatives (import,moved,removed); opt back in with one env var when you need to - Cross-platform — Linux, macOS, and Windows on
amd64andarm64(nowindows/arm64)
brew tap etsy/terraform-demux https://github.com/etsy/terraform-demux
brew install terraform-demuxThe formula installs the binary as terraform-demux and creates a terraform symlink, so you can keep typing terraform everywhere.
terraform -version- Grab the binary for your platform from the latest release.
- Drop it into a directory on your
$PATH. Either keep it asterraform-demux, or rename/symlink it toterraformif you want it to be invoked as the default.
reads required_version directly |
drop-in terraform (no shim) |
auto-installs new versions on first use | |
|---|---|---|---|
| terraform-demux | ✅ | ✅ | ✅ |
| tfenv | partial (via tfenv use min-required) |
shim wrapper | ❌ (tfenv install <ver> first) |
| tfswitch | ✅ | ❌ (manages a symlink you switch) | ✅ (interactive) |
terraform-demux's niche: you never run a demux subcommand or remember to install a version. You just run terraform.
| Env var | Purpose |
|---|---|
TF_DEMUX_LOG |
Any non-empty value enables verbose logging to stderr. By default logs are buffered and only printed if something goes wrong. |
TF_DEMUX_ARCH |
Override the architecture used to pick a Terraform binary. Common case: TF_DEMUX_ARCH=amd64 on Apple Silicon for older Terraform releases that have no native arm64 build. |
TF_DEMUX_ALLOW_STATE_COMMANDS |
1, true, or yes bypasses the state-command guard described below. |
TF_DEMUX_CACHE_HOME |
Override the cache directory (otherwise os.UserCacheDir()/terraform-demux/). Useful for sandboxes and CI; also handy on macOS, where XDG_CACHE_HOME is ignored by os.UserCacheDir. |
Suggested shell alias for amd64 invocations on Apple Silicon:
alias terraform-amd64="TF_DEMUX_ARCH=amd64 terraform"TF_DEMUX_* variables are stripped from the environment passed to the child Terraform process, so wrapper-internal config can't accidentally leak into providers or nested invocations.
Native Terraform refactoring blocks are safer and reviewable in code, so terraform-demux refuses these CLI commands by default on the Terraform versions where a block alternative exists:
terraform import— refused on Terraform>= 1.5.0(use theimportblock).terraform state mv— refused on Terraform>= 1.1.0(use themovedblock).terraform state rm— refused on Terraform>= 1.7.0(use theremovedblock).
When you really do need to run one of them, set the override:
TF_DEMUX_ALLOW_STATE_COMMANDS=true terraform state mv ...The guard matches positional Terraform subcommands only, so flag values like -var=action=import won't trigger it.
terraform-demux caches HashiCorp's release index and downloaded Terraform binaries under os.UserCacheDir()/terraform-demux/ (e.g. ~/Library/Caches/terraform-demux/ on macOS), split into http/ and bin/ subdirectories. Each binary is checksum-verified before it lands in bin/.
Set TF_DEMUX_CACHE_HOME to point the cache somewhere else.
PRs and issues welcome. Run go test ./... for the unit tests; ./test.sh exercises the end-to-end flow against the fixtures in testdata/. CI runs on Linux, macOS, and Windows on every push.
Apache-2.0 — see LICENSE.
