Choose a storage layout

nix-seal separates a secret's identity, ciphertext location, access policy, and runtime path. Organize files for the people maintaining the repository. Moving a ciphertext does not require renaming its secret ID or its application-facing runtime path.

Start with two directories

A small repository can use:

flake.nix
secrets/
  token.age
templates/
  app.env.template

Once public trust and repositoryRoot are configured:

nixSeal.secrets.token = { };
nixSeal.templates = [ "app.env" ];

The template contains public text such as TOKEN={{nix-seal:token}}. A user creates the value through nix-seal's authoring command; no plaintext credential belongs in either directory. A whole private configuration can be one .age file and needs no public template.

These defaults do not assume a host-directory convention or a configuration framework:

Declaration Default repository-relative source
Unscoped secret token secrets/token.age
Administrator-scoped secret token secrets/<administrator>/<secretScope>/token.age
Unscoped secret with shared = true secrets/shared/token.age
Administrator-scoped secret with shared = true secrets/<administrator>/shared/token.age
Public template app.env templates/app.env.template

Administrator-scoped ciphertext defaults keep independently managed namespaces apart. They preserve existing layouts. If a small project selects an administrator catalog but wants the flat tree above, set nixSeal.secretDirectory = "secrets" once. Public templates default to one repository-wide directory because the same public syntax can serve several targets, each with its own bindings and permissions.

Only declared names are loaded. Adding another .age or .template file never registers a secret, adds a consumer, or changes a recipient set. Declaring a missing ciphertext produces a pending creation entry. Declaring a missing public template fails with its expected filename; write that public file or use inline content.

Grow by ownership

For a repository with several targets, a centralized tree is easy to inspect:

secrets/
  shared/
    registry-token.age
  servers/
    web/database-password.age
    worker/queue-password.age
templates/
  services/web.env.template
  services/worker.env.template

For a repository already organized around modules, colocating files often makes changes easier to review:

hosts/server/
  default.nix
  secrets/database-password.age
  templates/database.conf.template
shared/
  registry.nix
  secrets/registry-token.age
  templates/registry.conf.template

Configure the first target's directories once:

nixSeal = {
  secretDirectory = "hosts/server/secrets";
  sharedSecretDirectory = "shared/secrets";
  templateDirectory = "hosts/server/templates";
  secrets.database-password = { };
  secrets.registry-token.shared = true;
  templates = [ "database.conf" ];
};

An individual secret's repository-relative source overrides its directory. A template's explicit source or inline content overrides its named file. This supports existing filenames and public templates beside their consumer module without copying files into a prescribed tree. All directory settings are relative to repositoryRoot, not the importing module or shell directory.

Shared Nix modules can own common declarations, and targets can import them. nix-seal does not need to discover those modules or prescribe their filenames. Use lowercase stable names and / for namespaces when needed. For example, templates."services/web.env" = { }; selects templates/services/web.env.template.

Use the intended output filename, including its format extension, followed by .template: app.toml.template, settings.json.template, or service.env.template. For conventionally extensionless files, use names such as ssh_config.template or allowed_signers.template. nix-seal preserves dots in declared names and does not guess a format or search for alternative extensions.

When renaming an existing public file, keep its logical name and set an explicit source, for example templates.app = ./templates/app.env.template;. This keeps the application's runtime path stable. New configurations can use templates = [ "app.env" ]; and config.nixSeal.templates."app.env".path directly.

Decide what should be shared

Keep one ciphertext per independently authorized and rotated value. Reuse that file when consumers need the same value and canonical recipient policy. Each target can still assign its own runtime owner, permissions, and service actions. If recipients or rotation requirements differ, use separate files even when their initial values happen to match. A directory named shared grants no access.

Share public templates when their syntax is identical. Different targets can bind the same placeholder to different ciphertexts. A shared template does not make those ciphertexts shared. Keep a target-specific template with its target when its syntax differs; avoid making one large conditional template describe unrelated applications.

Keep an entire settings bundle encrypted when its field names or structure are private, or when all values share one access and rotation policy. Split fields when independent reuse or access control warrants it. Templates insert literal bytes; they do not automatically escape the destination configuration language.

Keep runtime state outside the source tree

Commit canonical ciphertext, public templates, and public Nix declarations. Private identities, signing keys, plaintext, editor workspaces, and retained prompt answers belong outside the checkout and Nix store. Ciphertext caches are generated state, not another canonical source tree. Use the platform's cache and protected runtime defaults unless its deployment requires overrides.

Reorganize ciphertext by moving the encrypted bytes and updating source or the directory options. Preserve IDs when the logical secret is unchanged. Because plans bind ciphertext source paths, export fresh plans and provision matching signed target artifacts before deployment. Retain previous ciphertext cache generations for rollback. Public template moves with unchanged content still need evaluation so consumers and plans are checked together.

The layout adds no recursive directory scan or independent inventory. Evaluation reads declared ciphertexts and public templates; the Rust implementation retains its bounded template validation and runtime rendering. See the authoring guide for setup and creation commands.