Install Vizier Local
Vizier Local is a free download. It runs OpenTofu or Terraform for you, and refuses to run a module whose provenance it cannot check. This page takes you from an empty directory to a proven apply, then to running your own modules.
Install
On macOS and Linux the installer resolves the latest release, downloads the right archive for your machine, and refuses to install if the checksum does not match. A tool whose job is checking what you run has no business installing itself unverified.
URL=https://raw.githubusercontent.com/CyberCoreSystems/vizier/main/install.sh
curl -fsSL "$URL" | sh # the latest release
VIZIER_VERSION=1.6.0 curl -fsSL "$URL" | sh # or pin a version
VIZIER_BIN_DIR=~/.local/bin curl -fsSL "$URL" | shOn Windows, take the zip from the releases page and check it yourself. The installer stops with that instruction rather than guessing.
$v = "1.6.0"
$base = "https://github.com/CyberCoreSystems/vizier/releases/download/v$v"
Invoke-WebRequest "$base/vizier_${v}_windows_amd64.zip" -OutFile vizier.zip
Invoke-WebRequest "$base/checksums.txt" -OutFile checksums.txt
# compare against the published checksum before you unpack it
(Get-FileHash vizier.zip -Algorithm SHA256).Hash.ToLower()
Select-String "windows_amd64.zip" checksums.txt
Expand-Archive vizier.zip -DestinationPath . # put vizier.exe on your PATHConfirm it is there:
$ vizier version
vizier 1.6.0The engine
Vizier does not replace OpenTofu or Terraform - it runs one of them. Install OpenTofu (or Terraform) and make sure it is on your PATH. vizier info reports which one it found, which is the answer to most "it works on my machine" questions.
--tofu-bin or set VIZIER_TOFU_BIN. A named engine is a requirement, not a preference: if it cannot be run, Vizier stops and says so rather than falling back to whatever is on PATH, which would not be the engine you asked for.Your first module
Ask the catalog for a module and it prints the exact source line to paste. A catalog source always names a version: an unpinned module would change under your tree without your tree changing, which is the drift Vizier exists to catch.
$ vizier catalog list
aws-acm live_tested signed ACM Certificate (DNS-validated)
aws-s3-bucket live_tested signed AWS S3 Bucket (hardened)
...
183 module(s). `vizier catalog show <slug>` prints the source line to paste.
$ vizier catalog show aws-s3-bucket
slug: aws-s3-bucket
version: 1.1.0
status: live_tested
signed: true (catalog claim; `vizier verify` checks it)
sha256: 87b4ffc46b3964c6c7c004ebf75de5c5e9c0e509c82cc8af2254bb3282d3a005
source: iacbazaar://[email protected]Put that source in a vizier.hcl, in its own directory. That directory is a unit: one module, one state, one set of inputs.
terraform {
source = "iacbazaar://[email protected]"
}
inputs = {
bucket_name = "acme-artifacts-eu-north-1"
}Now check it before running anything. verify touches no cloud and needs no credentials: it asks the catalog what was published, checks the signature against a key compiled into the binary, and reports what it found.
$ vizier verify
[ok] . - [email protected] proven (live_tested, signature verified)signature verified means the signature was checked, not that the catalog claims one exists. live_tested means that module was really applied against a live cloud account, verified, and destroyed again before it was published - what that does and does not claim.
vizier scaffold iacbazaar://aws-s3-bucketwrites the same file for you and fills in the current version. A module's input list is part of its published contract, so run vizier login first if you want the inputs filled in too.Plan, apply, destroy
These are the commands you already know. Vizier verifies first, materialises the module, writes the backend if the unit declares one, and then runs the engine.
vizier plan # inside the unit directory
vizier apply
vizier destroy
vizier run-all plan # every unit in the tree, in dependency order
vizier verify # proof status only, no engine, no cloudApply and destroy prompt for confirmation. Where there is no terminal, they refuse rather than assume: CI has to pass --auto-approve and say so out loud, because silence is not consent.
Your own modules
Most teams have modules of their own. Vizier runs them, but it will not pretend to have checked something it has not: a module we did not publish has no signature from us. There are three ways forward, in descending order of evidence, and with none of them Vizier stops.
$ vizier plan
[BLOCK] . - git::https://github.com/acme/infra.git//vpc is not an
IaC Bazaar module, so there is nothing to verify it against.
Pin it with verify { source_sha256 = "..." }, allow-list its
prefix with allow_unverified, or set allow_any_source = true
to run unpinned code deliberately1. Pin the digest. You declare what the code should be, and Vizier checks what it fetched against that. This is real provenance, supplied by you rather than by us, and it needs no catalog and no signing key. Run it once unpinned and Vizier prints the digest to paste.
terraform {
source = "git::https://github.com/acme/infra.git//vpc?ref=v2.4.0"
}
verify {
source_sha256 = "9f2c...the digest vizier printed..."
}If the tree ever differs from that digest, the run stops and shows both values. A tag can be moved; a digest cannot.
2. Allow-list a prefix. allow_unverified = ["git::https://github.com/acme/"] covers a whole repository or organisation. Admitted, and recorded in the receipt as unverified.
3. Say so for the whole tree. allow_any_source = true means this tree runs code from anywhere. It is explicit, and the receipt says so.
Public registry modules
Modules from the public Terraform registry work, in both the plain and the tfr:// spellings. Pin the version in the source: a registry module with no version means whatever was published this morning.
terraform { source = "terraform-aws-modules/vpc/aws?version=5.1.0" }
terraform { source = "tfr:///terraform-aws-modules/vpc/aws?version=5.1.0" }
# git, an archive over HTTPS, S3, GCS and a local path all work too
terraform { source = "../../modules/vpc" }?ref=main, say - is refused rather than ignored. An ignored pin is worse than a rejected one, because the run looks pinned while running code nobody chose.Where state lives
A fetched module is a cache, unpacked into .vizier-module beside the unit, and it can be thrown away and fetched again. Your state is not a cache. With a local backend the engine writes terraform.tfstate next to the configuration, so it sits inside that same directory - and --source-update is careful to move it aside and put it back rather than delete it with the rest.
vizier plan --source-update # re-fetch the module, keep the state beside it
No changes. Your infrastructure matches the configuration.For anything shared, declare a remote_state block and let Vizier write the backend file. vizier backend creates and moves the state stores a tree writes to.
In CI
There is a GitHub Action, and worked examples for Jenkins, Azure Pipelines, AWS CodeBuild, Atlassian and Spacelift in the public repository. Pin a version in CI rather than taking the latest.
- uses: CyberCoreSystems/[email protected]
with:
action: plan
dir: envs/prod
verify-mode: enforceLeave verify-mode on enforce. warn still runs modules that failed the check and records the bypass in the receipt, which is a different thing from a pipeline that stops.
For a team
Everything above is one operator on one machine, answerable to the policy written in the tree. When the policy needs to belong to the organisation instead - roles, an evidence floor per environment, review before an apply, shared state locking and durable history - that is Vizier Central, which you host yourself.