DEV Community

Cover image for sops + age for Secret Management in Git: Encrypted Config Flow
Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

sops + age for Secret Management in Git: Encrypted Config Flow

When database passwords, API keys, or TLS certificates are accidentally committed to Git repositories, cleaning up these sensitive data from history can be a significant operational burden. Keeping sensitive data separate from source code is a fundamental security rule; however, moving them to a centralized secret server (such as HashiCorp Vault) can be an overly complex infrastructure cost for small and medium-sized projects or lean GitOps flows. The combination of sops (Mozilla's Secrets OVerlay Provider tool) and the modern asymmetric encryption tool age provides a practical solution by encrypting the values in configuration files and keeping the structure visible, thus enabling secure version control on Git. sops supports YAML, JSON, ENV, INI, and BINARY formats.

In this guide, we will explore how to store configuration files in YAML, JSON, or ENV format encrypted in a Git repository using sops and age, including steps for team access and automatic decryption in CI/CD environments.

ℹ️ Why Non-Plain Text Configuration?

sops encrypts only the values, not the entire file as a block. This way, the keys in YAML or JSON files remain readable. You can see which parameter has changed in Git commits, get diffs, and easily detect merge conflicts.

The Problem of Secret Storage in Git Repositories and the SOPS / Age Response

The most common method for excluding configuration files from Git is to ignore them using .gitignore. However, this approach leads to the sharing of environment variables required for the application among developers through insecure channels (Slack, email, chat software). Over time, it becomes impossible to track who gave which key to which environment. Additionally, in GitOps architectures, the principle of storing the infrastructure state in the Git repository as a single source of truth is compromised by the complete exclusion of sensitive data from repositories.

Alternative approaches to solving this problem have significant limitations:

  1. GPG / PGP-based tools (git-crypt, etc.): PGP key management is complex, and maintaining configurations and certificate trust chains is difficult. git-crypt encrypts the file content, so even the smallest change in encrypted files requires Git to store the entire file, which can make diffing inefficient or make patching difficult. Also, git-crypt does not support retroactive access revocation.
  2. Centralized Secret Vaults (HashiCorp Vault, AWS Secrets Manager): These are comprehensive and powerful solutions but require high availability (HA) setup, authentication policies, and maintenance costs. They are too heavy for small teams or independent projects.

The combination of sops and age offers a practical solution without creating a dependency on a centralized service, based on file-based and asymmetric key infrastructure. sops scans the configuration tree, leaves key names open, and encrypts only the values with a symmetric data encryption key (DEK), which is then enveloped (envelope encryption) with public keys produced by age using X25519 key exchange.

Diagram

What is Age (Actually Good Encryption) and How to Generate Key Pairs?

age is a simple, modern, and secure file encryption tool designed by Filippo Valsorda to replace GPG. It does not carry the burden of complex configuration options or old cryptographic algorithms from the past. Behind the scenes, age uses X25519 key exchange, ChaCha20-Poly1305 symmetric encryption, and HMAC-SHA256. age keys are small, readable, and in an open format.

Generating an age key pair is extremely simple. After installing the tool on your system, you can generate your first key with the age-keygen command.

# Installation on macOS with brew
brew install age sops

# Installation on Linux (Debian/Ubuntu)
sudo apt-get install age
# Downloading sops binary (Linux x86_64)
# The current stable version is 3.13.1.
curl -LO https://github.com/getsops/sops/releases/download/v3.13.1/sops-v3.13.1.linux.amd64
sudo mv sops-v3.13.1.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
Enter fullscreen mode Exit fullscreen mode

To generate the age key pair used for encryption and decryption, run the following command:

# Create the key directory
mkdir -p ~/.config/sops/age

# Generate the key pair and write it to a file
age-keygen -o ~/.config/sops/age/keys.txt
Enter fullscreen mode Exit fullscreen mode

This command produces a keys.txt file containing two critical pieces of information:

# created: 2026-08-13T10:00:00Z
# public key: age1ql3z7hjy54pw302mvpyqy54pw302mvpyqy54pw302mvpyqy54pw3s7x883
AGE-SECRET-KEY-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP9S8XYZ
Enter fullscreen mode Exit fullscreen mode
  1. Public Key (age1... starting): This public key can be shared with your team, Git repository, or configuration files. It is all that's needed to encrypt data.
  2. Secret Key (AGE-SECRET-KEY-1... starting): This secret key must never be committed to Git, must be kept secure, or added to CI/CD variables. It is the only component that can decrypt encrypted data.

⚠️ Private Key Security

If your keys.txt file is lost or deleted, it will be mathematically impossible to decrypt the configuration data encrypted with that key. It is recommended to keep a backup of the key in a secure password manager.

How SOPS (Secrets OVerlay Provider) Works?

sops applies the "Envelope Encryption" technique when processing files. Each time a file is encrypted, sops generates a random symmetric key (Data Encryption Key - DEK). All values in the file are encrypted with this DEK using AES256-GCM.

Then, this DEK is encrypted separately with the public keys of the intended recipients (defined in the configuration) and added to the file's sops metadata block.

# Example encrypted data structure
database:
    host: ENC[AES256_GCM,data:10.0.0.12,iv:...,tag:...,type:str]
    password: ENC[AES256_GCM,data:SecretPass123,iv:...,tag:...,type:str]
sops:
    kms: []
    age:
        - recipient: age1ql3z7hjy54pw302mvpyqy54pw302mvpyqy54pw302mvpyqy54pw3s7x883
          enc: -----BEGIN AGE ENCRYPTED FILE-----
               ...
               -----END AGE ENCRYPTED FILE-----
    lastmodified: "2026-08-13T10:15:00Z"
    mac: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
    version: 3.13.1 # Current sops version
Enter fullscreen mode Exit fullscreen mode

In the example above:

  • database.host and database.password keys are left open, but their values are encrypted in ENC[...] format.
  • Under the sops block, information about which age recipient was used and the enveloped DEK is stored.
  • MAC (Message Authentication Code): Protects the file's integrity. If someone tries to modify the unencrypted key names, sops will detect an integrity error during decryption.

Step-by-Step Setup and .sops.yaml Configuration

To avoid writing long command-line parameters (sops --age age1... -e config.yaml) every time, a rules file named .sops.yaml is added to the project root directory. This file determines which files or patterns are encrypted with which keys.

Create a .sops.yaml file in your project directory with the following content:

creation_rules:
  # Rules for staging environment
  - path_regex: secrets/staging/.*\.yaml$
    age: >-
      age1ql3z7hjy54pw302mvpyqy54pw302mvpyqy54pw302mvpyqy54pw3s7x883,
      age1dev2345678901234567890123456789012345678901234567890123456

  # More restrictive rule for production environment
  - path_regex: secrets/production/.*\.yaml$
    age: >-
      age1ql3z7hjy54pw302mvpyqy54pw302mvpyqy54pw302mvpyqy54pw3s7x883,
      age1prod987654321098765432109876543210987654321098765432109876
Enter fullscreen mode Exit fullscreen mode

This configuration automatically authorizes the specified age public keys for any YAML file under secrets/staging/ or secrets/production/.

File Creation and Encryption Workflow

Now, let's create a configuration file and manage it with sops.

1. Creating a New Encrypted File:

You can directly open and edit a new file through sops. Your default text editor (defined by the EDITOR environment variable) will open:

export EDITOR=vim
sops secrets/staging/app-config.yaml
Enter fullscreen mode Exit fullscreen mode

Enter and save the following data in the editor:

api_key: "sk_test_998877665544332211"
database:
  url: "postgres://user:supersecret@10.0.0.5:5432/app_db"
  pool_size: 20
Enter fullscreen mode Exit fullscreen mode

After saving and exiting, sops will encrypt the values according to the rules defined in .sops.yaml and save the file encrypted on disk.

2. Encrypting an Existing Plain Text File:

To encrypt an existing config.yaml file in place:

sops -e -i secrets/staging/app-config.yaml
Enter fullscreen mode Exit fullscreen mode

The -e (--encrypt) parameter indicates encryption, and -i (--in-place) means the result will be written directly back to the input file.

3. Reading and Decrypting an Encrypted File:

To view the encrypted file in plain text in the terminal:

# Set the environment variable with the path to your private key
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt

sops -d secrets/staging/app-config.yaml
Enter fullscreen mode Exit fullscreen mode

This command decrypts the file, but does not modify the file on disk.

💡 Usage with Kubernetes Secrets

sops encrypts all values by default. However, in Kubernetes manifest files, you might want only the data and stringData sections to be encrypted, leaving apiVersion, kind, and metadata open. For this, you can add encrypted_regex: '^(data|stringData)$' to your .sops.yaml file.

# Example .sops.yaml rule for Kubernetes manifests
creation_rules:
  - path_regex: k8s/.*\.yaml$
    encrypted_regex: '^(data|stringData)$'
    age: "age1ql3z7hjy54pw302mvpyqy54pw302mvpyqy54pw302mvpyqy54pw3s7x883"
Enter fullscreen mode Exit fullscreen mode

Integration with CI/CD and GitOps Pipelines

Encrypting files and storing them in Git is half the job. The other half is having CI/CD runners or GitOps operators (like ArgoCD or FluxCD) automatically decrypt these secrets during deployment.

GitHub Actions Integration

The following GitHub Actions workflow example decrypts a sops-encrypted YAML file during the workflow and performs application deployment:

name: Deploy Application

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Code Checkout
        uses: actions/checkout@v4

      - name: Install SOPS and Age
        run: |
          # Current stable version is 3.13.1.
          curl -LO https://github.com/getsops/sops/releases/download/v3.13.1/sops-v3.13.1.linux.amd64
          sudo mv sops-v3.13.1.linux.amd64 /usr/local/bin/sops
          sudo chmod +x /usr/local/bin/sops
          # Install age (may not be installed by default in GitHub Actions runners)
          sudo apt-get update && sudo apt-get install -y age

      - name: Decrypt Secrets and Deploy
        env:
          # Your AGE private key stored in GitHub Secrets
          SOPS_AGE_KEY: ${{ secrets.MY_APP_AGE_PRIVATE_KEY }}
        run: |
          # Decrypt the encrypted file and produce a temporary configuration file
          sops -d secrets/production/app-config.yaml > config.production.json

          # Start or deploy your application
          echo "Deployment started..."
          # ./deploy.sh config.production.json

          # Remove sensitive data when done
          rm config.production.json
Enter fullscreen mode Exit fullscreen mode

ArgoCD / FluxCD Integration

If you are using GitOps tools:

  • FluxCD: It has built-in support for sops. You define an age private key as a Secret object in Kubernetes. Flux will automatically decrypt files during the Kustomization or HelmRelease process if it encounters a decryption.provider: sops definition.
  • ArgoCD: You can use the ksops extension or ArgoCD's ConfigManagementPlugin mechanism to render sops files into plain text during the deployment process.

Team Key Rotation and Multi-Recipient Strategy

Teams grow over time, new developers join, or members leave. When a developer leaves, it's necessary to change all secrets and re-encrypt them with new key pairs. sops' multi-recipient architecture makes this process easier.

Scenario 1: Adding a New Team Member

Let's say a new team member, Ahmet, has a public key age1ahmet....

  1. Add Ahmet's public key to the relevant rule in .sops.yaml:
creation_rules:
  - path_regex: secrets/.*\.yaml$
    age: >-
      age1mustafa...,
      age1ahmet...
Enter fullscreen mode Exit fullscreen mode
  1. Update the existing encrypted files with the new key list:
sops updatekeys secrets/staging/app-config.yaml
Enter fullscreen mode Exit fullscreen mode

This command updates the file without changing the symmetric DEK, adding a new copy of the DEK enveloped with Ahmet's public key to the sops metadata area. Now, Ahmet can also decrypt this file with his private key.

Scenario 2: Key Rotation and Re-encryption

If a key is suspected to be compromised or due to periodic security requirements, both the symmetric data and recipient keys need to be completely renewed:

  1. Remove the old key from .sops.yaml.
  2. Re-encrypt all data with new symmetric keys using the -r (--rotate) flag:
sops -r -i secrets/staging/app-config.yaml
Enter fullscreen mode Exit fullscreen mode

This command decrypts the file, generates a new DEK, re-encrypts the data with the new DEK, and envelops it with only the currently valid recipient public keys defined in .sops.yaml.

Comparative Summary

The key characteristics of different secret management approaches are summarized in the following table:

Feature Plain Git + .gitignore HashiCorp Vault sops + age
Storage in Git Not Possible Not Possible Fully Compatible
Diff / History Support None None Value-level support
Infrastructure Dependency None High (Server/Cluster) Zero-dependency binary
Learning Curve Low High Low
Access Control (RBAC) None Very Detailed Public/Private key-based
Cost Free Operational Cost Free / Open Source

Conclusion

The combination of sops and age is a practical way to secure configuration files in software development and infrastructure management processes. Without the operational burden of complex centralized secret servers, and without compromising on GitOps principles or Git commit history, you can securely version all sensitive data.

When integrating sops into your project, pay attention to the following key points:

  • Add a valid .sops.yaml rules file to your project root.
  • Never commit the generated age private keys (keys.txt) to your Git repository; store them securely in a password manager or CI/CD secret storage.
  • Update key lists with sops updatekeys when team members change.
  • Define SOPS_AGE_KEY environment variables in your CI/CD workflows for automatic deployment processes.

Official Resources

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.