> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orcra.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Encrypting secrets in your manifest repository

> Commit SOPS-encrypted secrets directly to your manifest repository and let orun decrypt them at apply time using age keys stored only on each node.

orun integrates with [SOPS](https://github.com/getsops/sops) to let you commit encrypted secrets alongside your manifests without exposing sensitive values in your repository. The age private key lives only on the node — it is never stored in the manifest repository. When the node applies manifests, orun decrypts secrets at runtime using the local key and passes plaintext values to containers as environment variables.

## How secrets work

orun supports two ways to use encrypted secrets:

* **Whole-file**: Create a `secrets.yaml` file in the root of your manifest repository, encrypt the entire file with SOPS, and commit the encrypted result. orun decrypts the file at apply time and makes the key-value pairs available as environment variables to your deployments.
* **Inline**: Place individual `ENC[AES256_GCM,...]` values directly in a Deployment's `spec.env` map. orun detects these patterns and decrypts them in place before starting the container.

At apply time, orun runs a two-phase decryption process:

1. If `secrets.yaml` exists in the repository root, orun decrypts it first to cache the SOPS data key.
2. orun walks every Deployment's `env` map and decrypts any inline `ENC[...]` values using the cached data key.

## Key file location

orun resolves the age private key from the following sources, in priority order:

1. The path passed via the `--age-key-path` flag to `orun start`.
2. The `SOPS_AGE_KEY_FILE` environment variable on the node.
3. The default path: `/opt/orun/keys/age.key`.

Place your age private key at `/opt/orun/keys/age.key` on each node, or set `SOPS_AGE_KEY_FILE` to a custom path.

## Setup steps

<Steps>
  <Step title="Generate an age key pair">
    Install [age](https://github.com/FiloSottile/age) and generate a key pair on a secure machine.

    ```bash theme={null}
    age-keygen -o age.key
    ```

    The public key is printed to stdout. Keep the private key (`age.key`) secure — you will copy it to each node.
  </Step>

  <Step title="Add the public key to your SOPS config">
    Create or update `.sops.yaml` in the root of your manifest repository with the public key from the previous step.

    ```yaml theme={null}
    creation_rules:
      - age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
    ```
  </Step>

  <Step title="Create and encrypt your secrets file">
    Create a `secrets.yaml` file with your plaintext secrets:

    ```yaml theme={null}
    database_password: supersecret
    api_key: abc123
    ```

    Encrypt it with SOPS using your age public key:

    ```bash theme={null}
    sops --encrypt --age age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p secrets.yaml > secrets.yaml.enc
    ```

    Rename the output to `secrets.yaml` and delete the plaintext file before committing.
  </Step>

  <Step title="Commit the encrypted file">
    Commit the SOPS-encrypted `secrets.yaml` to your manifest repository. The file is safe to store in version control — without the age private key, the contents cannot be decrypted.

    ```bash theme={null}
    git add secrets.yaml .sops.yaml
    git commit -m "Add encrypted secrets"
    git push
    ```
  </Step>

  <Step title="Copy the private key to each node">
    Copy the age private key to each node at the default path or a custom path you configure.

    ```bash theme={null}
    ssh root@203.0.113.10 "mkdir -p /opt/orun/keys"
    scp age.key root@203.0.113.10:/opt/orun/keys/age.key
    ```

    Set restrictive permissions so only the orun process can read the key:

    ```bash theme={null}
    ssh root@203.0.113.10 "chmod 600 /opt/orun/keys/age.key"
    ```
  </Step>
</Steps>

## Inline encrypted values

Instead of a whole-file `secrets.yaml`, you can place SOPS-encrypted values directly in a Deployment's `env` map. Generate an encrypted value with SOPS and paste the `ENC[...]` string as the field value:

```yaml theme={null}
apiVersion: run.orcra.dev/v0alpha
kind: Deployment
metadata:
  name: my-app
spec:
  image: my-app:latest
  env:
    DATABASE_PASSWORD: ENC[AES256_GCM,data:abc123...,tag:xyz...,type:str]
    LOG_FORMAT: json
```

orun decrypts `ENC[...]` values before passing them to the container. Plain values like `LOG_FORMAT: json` are passed through unchanged.

<Note>
  Inline decryption requires the data key to be cached first. orun loads it by decrypting `secrets.yaml` in phase one. If `secrets.yaml` is absent and no data key is cached from a previous cycle, inline `ENC[...]` values cannot be decrypted.
</Note>

## Nested secrets and key flattening

If your `secrets.yaml` uses nested YAML structures, orun flattens nested keys into dot-separated paths when injecting them as environment variables. For example:

```yaml theme={null}
database:
  password: supersecret
  host: db.internal
```

Becomes the environment variables `database.password` and `database.host`.

## Reference

| Constant            | Value                    | Description                                                 |
| ------------------- | ------------------------ | ----------------------------------------------------------- |
| `DefaultAgeKeyPath` | `/opt/orun/keys/age.key` | Default path orun looks for the age private key on the node |
| `AgeKeyFileEnv`     | `SOPS_AGE_KEY_FILE`      | Environment variable that overrides the default key path    |

<Warning>
  Never commit unencrypted secrets to your manifest repository. If the age private key is absent or unreadable on the node, orun logs a warning and leaves encrypted values in place. Containers that receive raw `ENC[...]` strings as environment variable values will behave unexpectedly.
</Warning>
