Skip to content

Command Reference

Jake Paine edited this page May 23, 2026 · 5 revisions

Command Reference

All commands read keyseal.yaml from the current working directory (except init, which creates it). Cobra's SilenceUsage and SilenceErrors are set - usage is not printed on errors, and errors are handled by main() directly.


keyseal --version

Prints a single line with the version and short commit hash, then exits.

keyseal --version
# keyseal v1.0.0 (abc1234)

This is a root-level flag, not a subcommand. For more detail, use keyseal version.


init

keyseal init [flags]

Initializes a Keyseal repository in the current directory.

Creates keyseal.yaml and .sops.yaml, then creates the default directory scaffold:

production/platform/
production/infra/
production/tenants/
staging/platform/
staging/infra/
staging/tenants/

Flags

Flag Default Description
--profile string default Profile name written into the generated keyseal.yaml
--force false Overwrite existing files; continue even if directories already exist
--dry-run false Print planned actions without writing anything

Behavior

Without --force, init fails if keyseal.yaml or .sops.yaml already exists, or if any of the six directories already exist. Use --force to re-initialize or update the scaffold.

--dry-run prints the profile name and a list of directories and files that would be created, then returns without writing.

After init

The generated .sops.yaml contains placeholder age recipients (age1REPLACE_ME, age1RECOVERY_REPLACE_ME). These must be replaced with real age public keys before any encryption will work. Doctor will flag the placeholders until they are replaced.

Example

keyseal init
keyseal init --profile production
keyseal init --force
keyseal init --dry-run

add

keyseal add <logical-name> [flags]

Creates a new encrypted secret file for the given logical name.

The starter document is written to a secure temporary file (mode 0600) and passed to sops encrypt --filename-override <target>. Only the ciphertext is written to the final path. The temp file is removed on exit.

If sops.age_key_file is configured in keyseal.yaml, Keyseal passes that path through automatically unless SOPS_AGE_KEY_FILE is already set in the shell.

Arguments

Argument Description
logical-name The logical name of the secret (e.g. production/platform/app)

Flags

Flag Default Description
--kind string env Document kind. Only env is supported; any other value returns an error
--template string (empty) Built-in template to use for the starter values
--force false Overwrite an existing file
--commit false Stage the new secret file and create a Git commit
-m, --message string (empty) Git commit message. Implies --commit
--no-commit false Suppress commit even when git.auto_commit is enabled

Templates

If --template is not set, the starter document contains a single EXAMPLE_KEY: REPLACE_ME entry.

Available templates: laravel, stripe, mail, mysql-app. See Templates for the keys each template scaffolds.

Behavior

  1. Validates the logical name format
  2. Loads keyseal.yaml and resolves the target file path
  3. Builds the starter document from the template (or default single-key starter)
  4. Validates the document against the config's validation rules
  5. Calls sops encrypt via the configured binary
  6. Writes the ciphertext atomically to the target path
  7. Optionally stages only the new secret file and commits it

If a SOPS creation rule does not match the target path, SOPS will error out and no file is written.

Common mistakes

  • Passing a logical name that ends in .yaml or includes .enc. - Keyseal rejects this
  • Running add before replacing placeholder recipients in .sops.yaml - SOPS will error
  • Using a --template name that doesn't exist - returns unknown template "<name>"

Example

keyseal add production/platform/app
keyseal add production/platform/mail --template mail
keyseal add production/platform/app -m "Add production app secret"
keyseal add staging/platform/app --template laravel --force

edit

keyseal edit <logical-name> [flags]

Opens the encrypted file in SOPS's interactive editor. SOPS decrypts the file to a temp buffer, opens it in your $EDITOR (or its own default editor), and re-encrypts on save.

Arguments

Argument Description
logical-name The logical name of the secret to edit

Flags

Flag Default Description
--commit false Stage the edited secret file and create a Git commit if the file changed
-m, --message string (empty) Git commit message. Implies --commit
--no-commit false Suppress commit even when git.auto_commit is enabled

Behavior

The command resolves the logical name to a file path, then runs sops <path>. Your terminal is attached to the sops process directly - Keyseal does not intercept or buffer the interaction.

If the target file already exists but is empty or whitespace-only, Keyseal first bootstraps it with the standard encrypted starter document so SOPS has valid content to edit.

If sops.age_key_file is configured in keyseal.yaml, Keyseal passes that path through automatically unless SOPS_AGE_KEY_FILE is already set in the shell.

If commit behavior is enabled, Keyseal compares the encrypted file before and after SOPS exits. It only stages and commits the file if the contents actually changed.

If SOPS exits with a non-zero code, edit returns that error wrapped with context.

Example

keyseal edit production/platform/app
keyseal edit production/platform/app --commit
keyseal edit production/platform/app -m "Update app secret"

updatekeys

keyseal updatekeys [logical-name...] [flags]

Runs sops updatekeys for Keyseal-managed encrypted files so their recipients match the current .sops.yaml creation rules.

This command syncs recipients only. It does not rotate secret values and does not rotate the SOPS data encryption key.

Arguments

Argument Description
logical-name... One or more secrets to update. Omit only when using --all

Flags

Flag Default Description
--all false Discover every encrypted file under repository.root
--yes false Pass -y to sops updatekeys for non-interactive confirmation
--commit false Stage changed encrypted files and create a Git commit if the full batch succeeds
-m, --message string (empty) Git commit message. Implies --commit

Behavior

Before changing files, Keyseal validates keyseal.yaml, repository.root, .sops.yaml, SOPS recipient readiness, placeholder recipients, and the configured SOPS binary. This command requires the external SOPS CLI.

For each target file:

  • empty or whitespace-only placeholder files are skipped
  • missing files fail
  • non-empty plaintext files at encrypted paths fail
  • encrypted files run through sops updatekeys

The command continues after per-file failures, prints a final summary, and exits non-zero if any file failed. If commit behavior is enabled, Keyseal commits only when the whole batch succeeded and at least one file changed.

Unlike add, edit, and rollback, updatekeys does not honor git.auto_commit; commits are explicit only.

Example

keyseal updatekeys production/platform/app
keyseal updatekeys production/platform/app staging/platform/app --yes
keyseal updatekeys --all --yes
keyseal updatekeys --all --yes -m "Sync SOPS recipients"

status

keyseal status [logical-name]

Shows Git status for Keyseal-managed files only.

That includes:

  • encrypted secret files that match the configured encrypted extension
  • keyseal.yaml
  • .sops.yaml

The output uses Git's short two-column status format (XY path) so staged vs unstaged changes remain visible without showing unrelated repo noise.

If you pass a logical name, the output is narrowed to that single encrypted secret file.

Example

keyseal status
keyseal status production/platform/app
keyseal status --json

diff

keyseal diff <logical-name>

Resolves the logical name to an encrypted file path, then runs git diff -- <file>.

Example

keyseal diff production/platform/app

history

keyseal history <logical-name>

Resolves the logical name to an encrypted file path, then runs file-scoped Git history for that file.

Flags

Flag Default Description
--oneline false Show compact one-line Git history

Example

keyseal history production/platform/app
keyseal history production/platform/app --oneline

commit

keyseal commit [flags]

Stages current Keyseal-managed changes and creates a Git commit.

It stages only:

  • encrypted secret files
  • keyseal.yaml
  • .sops.yaml

It does not run git add ..

Flags

Flag Default Description
-m, --message string generated Commit message. If omitted, Keyseal uses a blunt count-based default

Behavior

If no Keyseal-managed files are changed, the command fails with nothing to commit.

Example

keyseal commit
keyseal commit -m "Rotate Stripe webhook secret"

rollback

keyseal rollback <logical-name> --to <commit> [flags]

Restores the encrypted file for the logical name from the specified Git commit.

Rollback is Git-based file restore. Keyseal does not reconstruct decrypted state manually.

Flags

Flag Default Description
--to string (required) Git commit to restore from
--dry-run false Show the target rollback without changing the working tree
--commit false Stage the restored file and create a Git commit
-m, --message string generated Git commit message. Implies --commit
--no-commit false Suppress commit even when git.auto_commit is enabled

Safety

  • If the target file has local changes, rollback fails
  • If the specified commit does not contain the file, rollback fails
  • --dry-run prints the intended action without modifying the working tree, even if the target file currently has local changes

Example

keyseal rollback production/platform/app --to abc1234
keyseal rollback production/platform/app --to abc1234 --dry-run
keyseal rollback production/platform/app --to abc1234 -m "Rollback app secret"

render

keyseal render <logical-name...> [flags]

Decrypts one or more secrets with the official SOPS Go decrypt library, merges their values maps, and writes the result to a file or stdout. This command does not require external sops or age binaries; it requires age private key material.

If sops.age_key_file is configured in keyseal.yaml, Keyseal passes that path to the decrypt library unless SOPS_AGE_KEY_FILE is already set in the shell.

At least one logical name is required. Multiple names are allowed - later names win on key conflicts. Empty or whitespace-only .enc.yaml files are treated as uninitialized placeholders and skipped. If every requested file is an empty placeholder, render fails with a clear error instead of producing empty output.

Flags

Flag Default Description
--format string defaults.output_format Output format: dotenv, json, or yaml
--out string (none) Write output to this file path
--stdout false Write output to stdout
--mode string defaults.file_mode File mode for --out (4-digit octal, e.g. 0600)
--force false Allow unsafe file modes; overwrite existing output file

--out and --stdout are mutually exclusive. One is required.

Formats

dotenv: KEY="VALUE"\n per entry, sorted alphabetically. Escape sequences applied: \\\, newline → \n, "\".

json: JSON object with string keys and values, indented with 2 spaces. Keys are sorted alphabetically by Go's json.MarshalIndent.

yaml: YAML mapping with all values tagged !!str (explicit string type). Keys are sorted alphabetically.

Safety check

When writing to a file, the mode is checked for group or world bits. If the mode is not owner-only (e.g. 0644) and --force is not set, render refuses with:

mode 0644 is not owner-only; use --force to override

Merge behavior

When multiple logical names are provided, their values maps are merged in order. If staging/platform/app has APP_ENV: staging and production/platform/app has APP_ENV: production, then:

keyseal render staging/platform/app production/platform/app --stdout
# APP_ENV will be "production" (last wins)

Example

# dotenv to file
keyseal render production/platform/app --out /run/secrets/app.env

# json to stdout
keyseal render production/platform/app --stdout --format json

# merge two secrets, yaml output
keyseal render production/platform/app production/platform/mail \
  --out /run/secrets/combined.yaml --format yaml

# allow unsafe mode
keyseal render production/platform/app --out ./app.env --mode 0644 --force

exec

keyseal exec <logical-name...> -- <command...>

Decrypts one or more secrets with the official SOPS Go decrypt library, merges their values, and injects them as environment variables into a subprocess. The -- separator is required. This command does not require external sops or age binaries; it requires age private key material.

If sops.age_key_file is configured in keyseal.yaml, Keyseal passes that path to the decrypt library unless SOPS_AGE_KEY_FILE is already set in the shell.

Arguments

At least one logical name before -- and at least one command token after --.

No flags

exec has no command-specific flags.

Behavior

  1. Classifies the named files first: empty or whitespace-only placeholders are skipped, non-empty files without SOPS metadata fail immediately as plaintext-at-encrypted-path errors
  2. Decrypts and merges the remaining usable secrets
  3. Merges secret values with the current environment (os.Environ()): secrets override any matching keys in the parent environment; all other parent environment variables are inherited
  4. Runs the command with the merged environment, with stdin/stdout/stderr attached to the current terminal
  5. If the subprocess exits non-zero, exec exits with the same code (no error message is printed)

If every requested file is an empty placeholder, exec fails with a clear error instead of launching the child process with no secret values.

Example

keyseal exec production/platform/app -- php artisan migrate
keyseal exec production/platform/app production/platform/mail -- ./start.sh
keyseal exec staging/platform/app -- env | grep APP_

Common mistakes

  • Forgetting the -- separator: Keyseal cannot distinguish between logical names and command arguments without it
  • Expecting exec to write a file: it does not; secrets exist only in the subprocess's environment

doctor

keyseal doctor [flags]

Runs health checks against the repository and reports findings. See Doctor for detailed coverage of each check.

Doctor validates config, SOPS availability, age availability warnings, .sops.yaml readiness, repository layout, plaintext mistakes, placeholder files, and decrypted document shape.

Flags

Flag Default Description
--json false Emit structured JSON instead of human-readable text

Exit codes

  • 0: all checks passed or produced only warnings
  • 1: one or more checks failed

Example

keyseal doctor
keyseal doctor --json
keyseal doctor --json | jq '.checks[] | select(.status == "fail")'

verify

keyseal verify [flags]

Runs the same non-mutating repository checks as doctor, but treats every warning as a failure. Decrypt validation uses the official SOPS Go decrypt library, so read-only CI/deploy runners need age key material but not external sops or age binaries.

Flags

Flag Default Description
--json false Emit structured JSON instead of concise text

Exit codes

  • 0: all checks passed with no warnings or failures
  • 1: one or more checks warned or failed

Example

keyseal verify
keyseal verify --json

version

keyseal version [flags]

Prints build metadata.

Flags

Flag Default Description
--short false Print only the version string

Output

Without flags:

keyseal v1.0.0 (abc1234)
tag: v1.0.0
commit: abc1234
built: 2026-04-19T13:10:00Z

With --short:

v1.0.0

For builds without a Git tag, Version is "dev". For builds without a known commit, the commit is "unknown" and is omitted from the one-line format.

Example

keyseal version
keyseal version --short

Clone this wiki locally