3 min read

Securing Secrets with SOPS and Age in Flux and K3s

Securing Secrets with SOPS and Age in Flux and K3s

If you've been following along with my previous GitOps setup guide, you know how to get Flux up and running. Now comes the important part: keeping your secrets actually secret.

In this post, I'll show you how to use SOPS and Age to encrypt sensitive data in your Git repository while still letting Flux decrypt and apply it to your cluster. It's easier than you might think!

What You'll Need

Before we get started, make sure you have:

  • A K3s cluster with Flux already installed
  • SOPS installed locally
  • Age installed locally
  • kubectl configured and ready to go

Assumptions:

  • Running on Linux x64 environment (these instructions are tailored for that)
  • Your repository structure should look something like this:
<REPOSITORY-NAME>/
├── apps/                       # Your application manifests
│   ├── . ../
│   └── homepage/               # This is where secrets.yaml lives
├── clusters/                   # Cluster-specific configuration
│   └── <CLUSTER-NAME>/
│       └── flux-system/
│           └── kustomization.yaml
└── README.md

Step 1: Install SOPS and Age

Let's start by getting the tools installed. First up is SOPS:

# Grab the latest SOPS release
LATEST_VERSION="$(curl -s https://api.github.com/repos/getsops/sops/releases/latest | jq -r '.tag_name')"
curl -LO https://github.com/getsops/sops/releases/download/${LATEST_VERSION}/sops-${LATEST_VERSION}.linux.amd64

# Move it to your PATH and make it executable
mv sops-${LATEST_VERSION}.linux.amd64 /usr/local/bin/sops
chmod +x /usr/local/bin/sops

Now let's install Age:

# Download and install Age
curl -L https://dl.filippo.io/age/latest?for=linux/amd64 -o age
mv age /usr/local/bin/age
chmod +x /usr/local/bin/age

Verify both are installed:

sops --version
age --version

Step 2: Generate Your Age Key Pair

Age uses a public/private key pair for encryption and decryption. Let's create one:

# Create the directory for your keys
mkdir -p ~/.age/

# Generate a new key pair
age-keygen -o ~/.age/keys.txt

This creates a private key file that Age will use to decrypt your secrets. Keep this file safe — anyone with access to it can decrypt your secrets.

Step 3: Set Up SOPS Configuration

Now we need to tell SOPS how to encrypt your secrets. First, set an environment variable:

export SOPS_AGE_KEY_FILE=~/.age/keys.txt

Pro tip: Add this line to your ~/.bashrc or ~/.zshrc so you don't have to set it every time you open a terminal.

Next, create a .sops.yaml file at your repository root with the following content:

creation_rules:
  - path_regex: apps/.*/secrets\.yaml$
    encrypted_regex: ^(data|stringData)$
    age: <YOUR AGE PUBLIC KEY HERE>

What's this doing? This tells SOPS to automatically encrypt the data and stringData fields in any secrets.yaml files under apps/<app-name>/. The Age public key is what SOPS uses to encrypt the data.

To get your Age public key, run:

age-keygen -y ~/.age/keys.txt

Copy the output and paste it into the age: field in .sops.yaml.

Step 4: Create and Encrypt Your Kubernetes Secret

Now let's create a secret file with your sensitive data. Create apps/homepage/secrets.yaml:

apiVersion: v1
kind: Secret
metadata: 
  name: homepage-secret
  namespace: homepage
data:
  api-token: YOUR_SECRET_VALUE_HERE

To encrypt this file, run:

sops --encrypt --in-place apps/homepage/secrets. yaml

If you open the file now, the data or stringData value look like gibberish — which is exactly what we want! That's your secret, encrypted.

Want to view or edit the decrypted content? SOPS makes that easy:

sops apps/homepage/secrets.yaml

SOPS will decrypt it temporarily in your editor, then re-encrypt it when you save. Pretty neat, right?

Step 5: Push Your Changes to Git

Now commit everything and push it up:

git add apps/homepage/secrets.yaml .sops.yaml
git commit -m "Add encrypted secrets using SOPS and Age"
git push origin main

Your encrypted secrets are now safely stored in Git. Even if someone gains access to your repository, they won't be able to read them without your Age private key.

Step 6: Configure Flux to Decrypt Secrets

Here's where the magic happens. We need to give Flux access to your Age private key so it can decrypt secrets when deploying applications:

kubectl create secret generic sops-age \
  --from-literal=age.agekey="$(cat ~/.age/keys.txt)" \
  -n flux-system

This creates a Kubernetes secret in the flux-system namespace containing your private key. Flux will use this to decrypt your encrypted YAML files.

Step 7: Verify Everything Works

Let's make sure everything is set up correctly:

# Force Flux to sync and apply your changes
flux reconcile source git flux-system -n flux-system
flux reconcile kustomization homepage

# Check if your secret was created in the cluster
kubectl get secret homepage-secret -n homepage -o yaml

You should see your secret now exists in the cluster with the decrypted values.