sops exec-env: Inject Secrets as Env Vars
sops exec-env decrypts a file and runs a command with the decrypted key/value pairs exported as environment variables.
exec-env keeps plaintext out of files entirely: it decrypts in memory and hands the values to a subprocess as env vars.
What it does
sops exec-env decrypts a flat key/value file (env or a single-level YAML/JSON map) and executes the given command with each pair set in its environment. Nothing decrypted is written to disk. exec-file is the sibling that materializes the plaintext as a temporary file and passes its path instead.
Common usage
# run a deploy with secrets as env vars, no plaintext on disk
sops exec-env secrets.enc.env 'terraform apply -auto-approve'
# the file-based variant for tools that read a file
sops exec-file secrets.enc.json 'myapp --config {}'Options
| Form | What it does |
|---|---|
| exec-env <file> <cmd> | Run cmd with decrypted values as env vars |
| exec-file <file> <cmd> | Run cmd with {} replaced by a temp plaintext file path |
| --no-fifo (exec-file) | Use a regular temp file instead of a FIFO |
| --user <name> | Drop privileges to this user for the command |
In CI
exec-env avoids ever writing a decrypted secrets file to the workspace, which is safer than sops -d > file on a shared runner. The decrypted values exist only for the child process. Note that exec-env needs a flat map; nested structures should use exec-file or be flattened first.
Common errors in CI
"Failed to get the data key required to decrypt" is the usual key-access failure (see sops --decrypt). "error: file is not a flat ... structure" means exec-env got nested data; flatten it or use exec-file. If the command runs but variables are empty, the file likely had the values under a nested key rather than at the top level.
Using this in CI
A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods
# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info
# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes