Zum Inhalt springen
SONOYA.com
Web & Code

sops und age: .env-Dateien verschlüsselt in Git

Passwörter und API-Schlüssel aus .env-Dateien mit sops und age verschlüsselt ins Git-Repository legen und auf dem Server direkt mit Docker Compose nutzen.

Von

Schloss-Symbol auf dunkelgrünem Verlauf für mit sops und age verschlüsselte .env-Dateien, die sicher in einem Git-Repository liegen

Jedes Projekt mit Docker Compose hat irgendwann eine .env-Datei mit Datenbank-Passwörtern, API-Schlüsseln und Tokens. Sie landet zu Recht in der .gitignore, denn im Klartext hat sie in einem Repository nichts verloren. Das Problem: Damit ist sie auch nicht versioniert. Auf dem Server liegt eine Version, auf dem Mac eine andere, und wenn der Server ausfällt, rekonstruierst Du die Passwörter aus dem Gedächtnis und alten Chatverläufen.

sops löst das elegant. Es verschlüsselt die Werte in der .env-Datei, lässt aber die Namen der Variablen lesbar. Die verschlüsselte Datei kann gefahrlos ins Repository, und in jedem Commit siehst Du, welche Variable sich geändert hat, ohne ihren Inhalt zu sehen. Entschlüsseln kann nur, wer einen passenden Schlüssel hat. Als Schlüssel nimmst Du age, ein kleines, modernes Verschlüsselungswerkzeug, das deutlich einfacher ist als PGP.

Diese Anleitung richtet sops und age auf dem Mac und auf einem Linux-Server ein, verschlüsselt eine .env-Datei und startet damit Docker Compose. Stand ist sops 3.13 und age 1.3. Grundlage ist die offizielle Dokumentation von sops zu age.

Wie das Prinzip funktioniert

Jeder, der die Datei entschlüsseln darf, hat ein eigenes Schlüsselpaar aus age. Der private Schlüssel bleibt auf seinem Gerät, der öffentliche beginnt mit age1 und kommt in eine Konfigurationsdatei im Repository. sops verschlüsselt die Werte so, dass jeder dieser privaten Schlüssel sie öffnen kann.

In dieser Anleitung sind das zwei Empfänger: Dein Mac und Dein Server. Kommt später ein Kollege oder ein zweiter Server dazu, ergänzt Du dessen öffentlichen Schlüssel.

sops und age installieren

Auf dem Mac installierst Du beide Programme mit Homebrew:

Code
brew install sops age

Auf einem Server mit Debian oder Ubuntu gibt es age in den Paketquellen. sops lädst Du als fertiges Programm von der Release-Seite auf GitHub. Für einen Server mit Intel- oder AMD-Prozessor sieht das so aus:

Code
sudo apt install age
curl -LO https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.linux.amd64
sudo install -m 755 sops-v3.13.3.linux.amd64 /usr/local/bin/sops

Auf einem Raspberry Pi oder einem anderen ARM-Server ersetzt Du in beiden Zeilen amd64 durch arm64. Prüfe mit sops --version, ob es geklappt hat.

Schlüssel erzeugen

sops sucht den privaten age-Schlüssel an einem festen Ort, und dieser Ort ist auf Mac und Linux unterschiedlich. Genau daran scheitern viele beim ersten Versuch.

Auf dem Mac liegt er laut sops-Doku unter ~/Library/Application Support/sops/age/keys.txt:

Code
mkdir -p "$HOME/Library/Application Support/sops/age"
age-keygen -o "$HOME/Library/Application Support/sops/age/keys.txt"

Auf dem Linux-Server liegt er unter ~/.config/sops/age/keys.txt:

Code
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt

age-keygen gibt jeweils eine Zeile mit Public key: age1… aus. Notiere Dir beide öffentlichen Schlüssel, vom Mac und vom Server. Den öffentlichen Schlüssel zeigt Dir auch später jederzeit age-keygen -y mit dem Pfad zur Schlüsseldatei an.

Sichere die beiden Dateien keys.txt sofort in Deinem Passwortmanager. Gehen alle privaten Schlüssel verloren, sind die verschlüsselten Dateien nicht mehr zu öffnen.

Die Regeln im Repository festlegen

Lege im Hauptordner Deines Repositorys die Datei .sops.yaml an. Sie sagt sops, welche Dateien mit welchen Schlüsseln verschlüsselt werden:

Code
creation_rules:
  - path_regex: \.env$
    age: >-
      age1macschluessel0000000000000000000000000000000000000000000,
      age1serverschluessel000000000000000000000000000000000000000

Ersetze die beiden Platzhalter durch Deine öffentlichen Schlüssel. path_regex ist ein regulärer Ausdruck, der hier auf alle Dateien passt, deren Name auf .env endet. Die Datei enthält nur öffentliche Schlüssel und darf ins Repository.

Die .env-Datei verschlüsseln

Angenommen, Deine .env sieht so aus:

Code
POSTGRES_PASSWORD=sehr-geheim-123
SMTP_PASSWORD=noch-geheimer-456
APP_URL=https://app.example.com

Verschlüssle sie in eine neue Datei:

Code
sops encrypt .env > secrets.enc.env

sops erkennt das Format an der Endung .env, findet über .sops.yaml die Empfänger und schreibt das Ergebnis in secrets.enc.env. Der Inhalt sieht danach etwa so aus, gekürzt:

Code
POSTGRES_PASSWORD=ENC[AES256_GCM,data:k3Jv…,type:str]
SMTP_PASSWORD=ENC[AES256_GCM,data:9xQa…,type:str]
APP_URL=ENC[AES256_GCM,data:Hc0p…,type:str]
sops_age__list_0__map_recipient=age1mac…

Die Variablennamen bleiben lesbar, die Werte sind verschlüsselt, und am Ende stehen Angaben zu den Empfängern. Sorge jetzt dafür, dass die Klartext-Datei nie im Repository landet, und committe die verschlüsselte:

Code
echo ".env" >> .gitignore
git add .gitignore .sops.yaml secrets.enc.env
git commit -m "Verschlüsselte Umgebungsvariablen"

Werte ändern

Zum Bearbeiten musst Du die Datei nicht von Hand entschlüsseln. sops öffnet sie entschlüsselt in Deinem Editor und verschlüsselt sie beim Speichern wieder:

Code
EDITOR=nano sops edit secrets.enc.env

Ändere einen Wert, speichere und schließe den Editor. Im Git-Diff siehst Du danach, dass sich genau diese eine Zeile geändert hat, aber nicht, wie der neue Wert lautet.

Auf dem Server mit Docker Compose nutzen

Auf dem Server klonst Du das Repository wie gewohnt. In Deiner compose.yml verwendest Du die Variablen mit der üblichen Schreibweise:

Code
services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

Zum Starten lässt Du sops die Werte entschlüsseln und direkt an Docker Compose übergeben:

Code
sops exec-env secrets.enc.env 'docker compose up -d'

exec-env entschlüsselt die Datei im Arbeitsspeicher, setzt die Werte als Umgebungsvariablen und startet damit den angegebenen Befehl. Auf der Festplatte des Servers liegt zu keinem Zeitpunkt eine Klartext-Datei. Prüfe mit docker compose config, ob die Werte ankommen. Rufe auch diesen Befehl über sops exec-env auf, sonst sind die Variablen leer.

Brauchst Du doch eine echte .env-Datei, weil ein Programm sie zwingend einliest, entschlüsselst Du sie und schützt sie anschließend:

Code
sops decrypt secrets.enc.env > .env
chmod 600 .env

Nutzt Du Coolify, verwaltet Coolify die Umgebungsvariablen in seiner eigenen Oberfläche. Das verschlüsselte Repository ist dann Deine versionierte Quelle, aus der Du Werte bei Bedarf überträgst.

Einen weiteren Schlüssel hinzufügen

Kommt ein neuer Server oder ein Kollege dazu, erzeugt er sich ein Schlüsselpaar und schickt Dir seinen öffentlichen Schlüssel. Ergänze ihn in .sops.yaml mit einem Komma in der Liste und aktualisiere dann die verschlüsselte Datei:

Code
sops updatekeys secrets.enc.env

sops zeigt Dir die Änderungen an und fragt nach einer Bestätigung. Um jemandem den Zugang zu entziehen, entfernst Du seinen Schlüssel auf dieselbe Weise. Wichtig dabei: Wer die Datei vorher entschlüsseln konnte, kennt die alten Werte. Nach dem Entzug änderst Du deshalb auch die Passwörter selbst.

Häufige Fehlermeldungen

no matching creation rules found. Der Dateiname passt nicht zu path_regex in der .sops.yaml, oder sops findet die Datei nicht, weil Du in einem anderen Ordner bist. sops sucht .sops.yaml im aktuellen Ordner und in den Ordnern darüber.

failed to get the data key required to decrypt the SOPS file. sops findet keinen passenden privaten Schlüssel. Auf dem Mac liegt die Datei meist noch unter ~/.config/sops/age/, wo sops sie nicht sucht. Verschiebe sie nach ~/Library/Application Support/sops/age/ oder setze die Umgebungsvariable SOPS_AGE_KEY_FILE auf den Pfad.

Die Klartext-.env ist schon einmal in Git gelandet. Dann liegt sie in der Historie, auch wenn Du sie später löschst. Betrachte alle darin enthaltenen Passwörter als bekannt, ändere sie und verschlüssle erst danach neu.

Unterm Strich

sops und age kosten Dich eine Viertelstunde Einrichtung und lösen ein Problem, das fast jedes Selfhosting-Projekt hat: Die Zugangsdaten sind versioniert, nachvollziehbar und trotzdem geschützt. Ein neuer Server ist damit in Minuten aufgesetzt, denn Repository klonen, Schlüssel ablegen und sops exec-env reichen. Wenn Du den Server dafür ohnehin neu aufsetzt, hilft Dir die Entscheidungshilfe Proxmox: Docker im LXC-Container oder in einer VM? bei der Grundlage.

Weiterlesen.