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 yamlYou should see your secret now exists in the cluster with the decrypted values.