Guides

Encrypt Your Secrets in Git with SOPS and age

Every team eventually faces the same moment: someone needs a credential, so it gets pasted into a chat window, dropped into a ticket, or committed to a config file with a note promising to clean it up later. The threat is not only the person you sent it to. It is the copy in the chat archive, the copy in the repository history, the copy in a laptop backup, and the copy in a CI log. Once a secret is written in readable form, you cannot reliably take it back. This guide shows how to keep secrets in the same repository as your code while storing only encrypted values, so a leaked repository or a stolen laptop does not hand over your credentials.

Steps

  1. Generate an age key pair on your own machine. Use the age key generation tool and write the output to a file such as key.txt. The tool prints a public key, which is the only part you will ever share, and stores the private key in the file. The threat here is key custody: anyone who obtains that private key can decrypt every file that was encrypted to its public key, so treat the file as the most sensitive object in this workflow.
  2. Move the private key off the repository and into protected storage. Put it in a password manager or on an encrypted disk, and never inside the project directory. This defends against the most common failure, which is committing the key alongside the data it protects, and against device theft, where full disk encryption is what buys you time.
  3. Install SOPS and create a configuration file at the repository root. SOPS encrypts the values inside YAML, JSON, ENV and similar files while leaving the key names readable, so diffs stay reviewable and you can see which secret changed without seeing its contents. The configuration file maps file paths to the age public key that should be used, so nobody has to remember the right recipient by hand.
  4. Encrypt the file in place. Run SOPS in encrypt mode against your secrets file so the plaintext is replaced by ciphertext. Confirm the result by opening the file: keys should be visible, values should be wrapped in an encrypted block. This step defends against accidental disclosure through code review, screenshots and repository mirrors, because there is nothing readable left to disclose.
  5. Commit only the encrypted file and block the plaintext variants. Add ignore rules for the key file, for any decrypted copies, and for editor backups. This defends against the quiet leak, where a local plaintext file gets swept into a commit by a broad add command.
  6. Decrypt on another machine by pointing SOPS at your key. Copy the private key to the new machine through a secure channel, set the environment variable that tells SOPS where the key file lives, and run SOPS in decrypt mode. This defends against the temptation to share plaintext secrets between machines, which reintroduces every problem the encrypted file was meant to solve.
  7. Give automation its own key. For continuous integration, generate a separate age key, store it as a masked secret in the build system, and add its public key as an additional recipient. This defends against a build compromise spreading to your personal key, and it means you can revoke automation access without touching your own credentials.
  8. Rotate when anything is exposed. If a key file leaks, generate a new age key, update the configuration file, and run SOPS in update keys mode so the existing encrypted files are re wrapped to the new recipient. Rotation is the step people skip, and it is the only step that limits damage after a leak.

Two habits make this workflow hold up. First, check your history rather than trusting your intentions: search the repository log for the names of the secrets you care about and confirm that no readable value was ever committed, because encryption added today does not clean up a commit from last year. Second, keep the scope small. Encrypt the specific files that hold credentials instead of whole directories, so the encrypted output stays readable enough that a reviewer notices when something unexpected appears. Teams that run on cloud infrastructure can point SOPS at a managed key service instead of age, which removes the need to distribute a key file at all, but the underlying rule does not change: the plaintext should exist only where it is being used, and never in the repository.

Sources