Skip to content

Repository Layout

Jake Paine edited this page Apr 20, 2026 · 1 revision

Repository Layout

What keyseal init creates

Running keyseal init from your repository root produces this structure:

your-repo/
  keyseal.yaml
  .sops.yaml
  production/
    platform/
    infra/
    tenants/
  staging/
    platform/
    infra/
    tenants/

This is the default scaffold - you are not required to use this exact layout. The directories are created as empty placeholders. What actually matters to Keyseal is the position of keyseal.yaml, the configured repository.root, and the SOPS creation rules in .sops.yaml.

keyseal.yaml

Must be present in the directory where you run Keyseal commands. Every command (except init) calls keyseal.yaml from the current working directory. There is no global config file and no flag to specify an alternate path.

See Configuration Reference for the full schema.

.sops.yaml

Controls which SOPS creation rules apply when Keyseal calls sops encrypt. The default template generates two rules - one for production/ and one for staging/ - with placeholder age recipients.

You must replace the placeholders before any encrypt operation will succeed:

creation_rules:
  - path_regex: production/.*\.enc\.yaml$
    age: age1abc123...,age1recovery456...
  - path_regex: staging/.*\.enc\.yaml$
    age: age1abc123...,age1recovery456...

SOPS matches creation rules against the destination path (or the --filename-override value during keyseal add). If no rule matches a given path, SOPS will refuse to encrypt.

Secret files

Each secret is an encrypted YAML file. By default, the extension is .enc.yaml. You can change this in keyseal.yaml under repository.encrypted_extension, but the extension must contain .yaml and start with ..

Files are named by their logical name:

production/platform/app.enc.yaml  ←  logical name: production/platform/app
staging/infra/redis.enc.yaml      ←  logical name: staging/infra/redis

The file content when decrypted is a structured YAML document - see Secret File Format.

Example tree after populating secrets

your-repo/
  keyseal.yaml
  .sops.yaml
  production/
    platform/
      app.enc.yaml
      mail.enc.yaml
    infra/
      redis.enc.yaml
    tenants/
      acme.enc.yaml
  staging/
    platform/
      app.enc.yaml
    infra/
      redis.enc.yaml

Logical name mapping

A logical name like production/platform/app maps to the file at <repository.root>/production/platform/app.enc.yaml. Keyseal validates that every logical name:

  • Is a relative path (no leading /)
  • Contains no backslashes
  • Does not include .enc. or end in .yaml
  • Is normalized (no .., no empty segments, no ./ prefix)
  • Does not escape the repository root via path traversal

PathToLogicalName and LogicalNameToPath are strict inverses - doctor validates that round-trips succeed for all discovered files.

Files Keyseal does not manage

The following are expected to exist in your repository but are not created or modified by Keyseal after init:

  • .sops.yaml - you own this; Keyseal reads it only for doctor checks
  • keyseal.yaml - you own this after init
  • Your age key files - typically outside the repo in ~/.config/sops/age/keys.txt

Files that should not be in the repo

  • The keyseal binary itself (doctor warns if it finds <repo_root>/keyseal)
  • The dist/ directory (doctor warns if it finds a non-empty <repo_root>/dist/)
  • Rendered output files (dotenv, JSON, YAML render output should not be committed)

Your .gitignore should exclude rendered output. The default .gitignore in the Keyseal source repo excludes /run/secrets/ and *.rendered as examples.

Clone this wiki locally