The following are general SOPS CLI examples; supported subcommands and argument placement depend on the installed version. Private-key read permissions and machine configuration determine whether administrator privileges are needed.
Subcommand overview#
sops edit 编辑加密文件(最常用,解密→编辑器→自动加密)
sops set 设置某个 key 的值
sops unset 删除某个 key
sops decrypt 解密输出到 stdout / -i 原地写回
sops encrypt 加密(需指定 recipient)
sops filestatus 检查文件加密状态
sops updatekeys 按 .sops.yaml 同步接收者
sops rotate 生成新数据密钥重加密
sops exec-env 解密后注入环境变量执行命令Editing plaintext: the core workflow#
Method 1: sops edit (recommended, standard workflow)
sops edit host/secrets/secrets.yamlIt automatically decrypts → opens $EDITOR to edit plaintext → automatically encrypts and writes back after saving. You never handle the ciphertext directly, and there is no need to manually decrypt, edit, and encrypt.
Editing permissions Administrators should first confirm the decryption identity and file write permissions. A dedicated user age identity avoids relying on the system host key for everyday editing. Do not put private keys in the repository or command arguments.
Method 2: edit in a restricted temporary file, then encrypt
# Bash 示例;在含 .sops.yaml 的配置仓库根目录中执行
# 先确认私钥可用,且解密目标与 creation_rules 的路径匹配
umask 077
task_secret_dir=$(mktemp -d "${TMPDIR:-/tmp}/sops-edit.XXXXXXXX")
trap 'rm -rf -- "$task_secret_dir"' EXIT
sops decrypt host/secrets/secrets.yaml > "$task_secret_dir/plain.yaml" &&
"${EDITOR:-nano}" "$task_secret_dir/plain.yaml" &&
sops encrypt --filename-override host/secrets/secrets.yaml \
"$task_secret_dir/plain.yaml" > "$task_secret_dir/encrypted.yaml" &&
sops filestatus "$task_secret_dir/encrypted.yaml" &&
cp -- "$task_secret_dir/encrypted.yaml" host/secrets/secrets.yaml
# 成功或失败退出后都清理私有临时目录;不要提交明文Delete temporary plaintext Use a randomly named private directory and 0700/0600 permissions for temporary files, set an exit cleanup hook, and prevent leaks through editor backups, swap files, and logs. Deletion does not guarantee secure erasure from the storage medium; choose an appropriately protected memory filesystem when needed. If encryption fails, do not overwrite the original ciphertext.
Method 3: set/unset (change one key without editing the entire file)
# 设置 app_env 的值(value 是 JSON 字符串)
sops set host/secrets/secrets.yaml '["app_env"]' '"NON_SECRET_EXAMPLE_VALUE"'
# 删除某个 key
sops unset host/secrets/secrets.yaml '["app_env"]'Encryption, decryption, and inspection#
# 解密到 stdout
sops -d host/secrets/secrets.yaml
# 解密并提取某个 key
sops -d --extract '["app_env"]' host/secrets/secrets.yaml
# 原地解密成明文文件(危险,别提交 git)
sops -d -i host/secrets/secrets.yaml
# 加密一份明文文件(需指定 recipient,或依赖 .sops.yaml)
sops -e --age "$PUB_KEY" "$task_secret_dir/plain.yaml" > "$task_secret_dir/encrypted.yaml"
# PUB_KEY 是已核对的 recipient;先检查加密成功再替换目标密文
# 检查文件是否加密
sops filestatus host/secrets/secrets.yamlKey and recipient management#
Add a recipient (authorize multiple machines):
# 1. 在 .sops.yaml 的 keys 添加新公钥,creation_rules 里加上
# 2. 用 updatekeys 同步(需有旧 key 权限解密)
sops updatekeys host/secrets/secrets.yamlRemove a recipient: remove that key from .sops.yaml, then run sops updatekeys.
Rotate the data key (generate a new AES key and re-encrypt; existing recipients remain valid):
sops rotate --in-place host/secrets/secrets.yamlA safe key-change workflow Add the new recipient first (keep both as a safeguard) → verify that the new key can decrypt → remove the old recipient only after confirming stability. During the transition, rollback is possible if you retain the old ciphertext, old keys, and a working system recovery path. See the sops migration guide for the full process.
Understanding .sops.yaml#
.sops.yaml is the sops client configuration file. It determines which public key encrypts which file. A general example follows (placeholders are not valid keys):
keys:
- &server-key AGE_RECIPIENT_PLACEHOLDER_NOT_VALID
creation_rules:
- path_regex: host/secrets/secrets\.yaml$
key_groups:
- age:
- *server-keyField-by-field meaning:
| Field | Meaning |
|---|---|
keys: |
A helper area for storing YAML anchors, not an independent SOPS authorization field |
&server-key |
YAML anchor: assigns a label to a public-key string |
creation_rules: |
An array of encryption rules, each matching certain files |
path_regex: |
A path regular expression determining which files a rule applies to |
key_groups: |
Key groups; within a group, any one identity can usually obtain the group’s share, while the Shamir threshold governs recovery across groups |
*server-key |
YAML alias: resolves to the full public key |
Multiple secrets files: creation_rules is an array, so you can add rules matching different files. Filenames are arbitrary as long as path_regex matches. Declare the runtime mapping in host/sops.nix using sopsFile:
sops = {
defaultSopsFile = ./secrets/secrets.yaml;
secrets = {
another = { sopsFile = ./secrets/another.yaml; };
};
};The roles of
.sops.yamlandhost/sops.nix.sops.yamlcontrols which public key is used during encryption (CLI side);host/sops.nixcontrols which secret is taken from which file during decryption (runtime side). Their recipient and private key must match.
Regex vs. glob: * is a repetition operator, not a wildcard
In a regular expression (regex), * means repeat the preceding character, rather than the shell glob meaning of “match anything”:
secrets\.yaml$ # 精确:只匹配 secrets.yaml(\. 转义点号,$ 锚定结尾)
.*secrets.* # 任意路径含 "secrets" 的都匹配(.* 才是 glob 的 *)
secrets-[0-9]+\.yaml$ # secrets-1.yaml, secrets-2.yaml...YAML’s & / * are not pointers
&name (anchor) and *name (alias) are YAML declarations/references, not memory pointers. They express an anchor/alias relationship between YAML nodes: *server-key refers to the previously defined public-key node during parsing. Their purpose is to define once, reference many times, so a change needs to be made only at the & definition. “Variable-like” is a better analogy than “pointer.”
The layers behind the syntax
.sops.yaml has two layers of logic:
| Layer | Defined by | Examples |
|---|---|---|
| Format layer (YAML syntax) | The YAML specification | &anchor, *alias, indentation, lists, and key-value pairs |
| Semantic layer (configuration fields) | Defined by sops | creation_rules、path_regex、key_groups、age |
Anchors and aliases are YAML features; sops merely uses them. Fields such as path_regex are where sops-specific logic begins. This distinction applies to any tool that uses YAML for configuration.
How .sops.yaml is located: check the working directory and explicit configuration
By default, the current upstream CLI searches upward for configuration from the process’s current directory, . (configuration-path environment variables may also affect this), not from the target file’s directory. When invoking it across directories, run it from the repository root or specify the configuration explicitly with --config /path/to/config-repo/.sops.yaml. path_regex interprets the target path relative to the configuration file’s directory. When re-encrypting a temporary file, use --filename-override to specify the intended repository filename so the rules match.
The roles of .sops.yaml and sopsFile in sops.nix
.sops.yaml |
In sops.nix: sopsFile |
|
|---|---|---|
| Who reads it | SOPS CLI | The sops-nix module |
| What it controls | Encryption/recipient-update rules and CLI storage options | Which source to decrypt at runtime and which secret to export |
| When it is needed | Creating ciphertext, updating authorization, and similar operations | Deploying secrets during system activation |
SopsFile does not determine the recipient used for CLI encryption. Existing ciphertext carries key-wrapping metadata, so decryption itself does not depend on creation_rules. However, .sops.yaml can also define storage or editing options, so it cannot be described as being read at only two points.
Quick reference for NixOS#
Command-line decryption and sops-nix runtime decryption use different entry points. sops-nix imports SSH keys through age.sshKeyPaths, whereas the SOPS CLI should explicitly use a readable age identity. --age takes an encryption recipient; a private-key file or process-substitution path cannot be supplied there in place of a decryption identity.
# 管理员在已授权读取 host key 的受控 shell 内执行;不要打印私钥
umask 077
task_key_dir=$(mktemp -d /run/sops-key-check.XXXXXXXX)
trap 'rm -rf -- "$task_key_dir"' EXIT
ssh-to-age -private-key -i /etc/ssh/ssh_host_ed25519_key > "$task_key_dir/identity.txt" &&
SOPS_AGE_KEY_FILE="$task_key_dir/identity.txt" \
sops decrypt host/secrets/secrets.yaml > /dev/null
# 检查退出码;不将明文或派生私钥写到终端This is only an executable example; it was not run on the current machine. If other default age identities exist, disable those additional identities in an isolated, controlled environment before testing the target private key, to avoid a false positive.
# 经授权激活后检查运行时产物,不读取内容
ls -la /run/secrets/app_env
systemctl status example-app.serviceThe JSON value passed to set can enter shell history or process arguments; the example uses only non-secret strings. For real values, prefer sops edit or a controlled file/standard-input interface supported by the current version. sops -d -i turns the original repository file into plaintext and should generally be avoided. If used by mistake, stop committing and restore protected ciphertext. filestatus checks the encryption status recorded in the format; it does not prove that there have been no leaks, that decryption works, or that runtime deployment succeeded.
Related articles#
- Principles → How sops works
- Runtime → sops-nix integration
- Key changes and migration steps → sops migration guide
References & further reading
- Official SOPS documentation ↗
- SOPS CLI source ↗
- SOPS configuration source ↗
- Official sops-nix documentation ↗
- sops-nix module ↗
- sops-nix templates module ↗
- Official ssh-to-age documentation ↗
Adapted from technical notes. Examples use generic names.