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

# Automatic volume unlock

> Prepare persistent-volume keys in your AWS account and authorize boot-time release through your own keyserver.

## What automatic unlock means

Automatic unlock delivers the same volume key on every authorized, non-debug enclave boot.
Your private keyserver verifies the enclave, reads the key from your secret store, and releases
it directly to the enclave. The guest must unlock and mount the disk before the workload starts.
You do not send a decrypt command after each Deploy.

This requires setup. A disk attachment, a named mount, or a saved CLI profile alone does not
enable automatic unlock or prove that the filesystem is unlocked. Without an unlocked volume
mounted at the application's configured path, writes to the enclave's ephemeral filesystem
do not survive Stop followed by Deploy or replacement during Update.

The CLI provisioning workflow uses **customer-owned AWS Secrets Manager**. It is not a
Tinfoil-managed key service. You operate the private keyserver, its TLS endpoint, and its
release policy. There is no shortcut that uploads the volume key to Tinfoil's secret store.

<Warning>
  An existing volume needs its original key. Never generate a new key to enable automatic
  unlock for an existing disk. Changing the secret value does not re-encrypt its contents;
  see [volume-key rotation](/containers/encrypted-volumes#volume-keys-are-not-ordinary-rotating-secrets).
</Warning>

## Prepare customer infrastructure

Before provisioning a key, arrange:

* A customer AWS account, region, and secret-name prefix, with durable retention and a recovery
  plan for the original key. Do not enable automatic rotation for volume secrets.
* A reachable [private keyserver](/containers/private-secrets#requirements) with a publicly
  trusted TLS certificate. Its `/challenge` and `/fetch` endpoints must be served directly,
  without redirects. Keep it available for unattended boots.
* A keyserver configured with the AWS backend, the same region and prefix as the CLI profile,
  and a customer-controlled policy loaded at keyserver startup.
* The exact config repository, intended release tag, and instance domain. The policy must pin
  all three; do not guess a domain or remove a pin to make a boot succeed.
* An application image and a declared named volume mount in `tinfoil-config.yml`. Preparing
  unlock configuration does not replace your application image or copy ephemeral files to disk.

### Separate provisioning from release permissions

Use separate customer identities for setup and the running keyserver:

| Identity | AWS access | Purpose |
| - | - | - |
| CLI provisioning operator | Scoped `secretsmanager:CreateSecret`, `secretsmanager:TagResource`, `secretsmanager:DescribeSecret`, and `secretsmanager:GetSecretValue` | Create a new key secret with provenance tags, or check and read the original secret for setup/recovery |
| Private keyserver | Scoped `secretsmanager:GetSecretValue` | Retrieve approved keys at boot; no secret creation or update access |

Existing-key setup needs Describe/Get access, not Create/Tag. Reading an existing secret
encrypted with a customer-managed KMS key also requires `kms:Decrypt`. New secrets use AWS's
default Secrets Manager encryption key; the helper does not offer custom KMS key selection.
The customer grants permissions. Local preflight is not an AWS write-permission dry run:
Create/Tag authorization remains unproven until the actual `CreateSecret` call succeeds.
CLI configuration does not create IAM roles, grant access, or deploy the keyserver.
Scope permissions to the intended secrets and KMS key rather than
granting account-wide secret administration.

The CLI uses the AWS SDK to create a new secret, not a plaintext shell command. It does not
overwrite an existing secret with an update/upsert operation. Keep key values out of command
arguments, logs, Git, and support messages. AWS documents the permission requirements for
[CreateSecret](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_CreateSecret.html),
[DescribeSecret](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DescribeSecret.html),
and [GetSecretValue](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html),
and the [exposure risks of plaintext command arguments](https://docs.aws.amazon.com/secretsmanager/latest/userguide/security_cli-exposure-risks.html).

## Local profiles and key custody

A project storage profile is scoped to the normalized controlplane URL, authenticated
organization ID, and canonical config repository. A per-volume receipt also binds the volume
UUID. Switching organizations, controlplanes, or repositories does not reuse another scope's
profile. Setup requires an organization login, not a personal context.

Profiles and receipts contain metadata, not volume keys or AWS credentials. An AWS profile
name is only a reference to your AWS credential configuration. The files live in a separate
`storage/` directory beside the effective CLI config file; the directory uses mode `0700` and
JSON files use mode `0600`. Login and logout do not rewrite this storage metadata.

Only new auto-unlock volume creation generates a random 64-byte key. The key is stored in
AWS Secrets Manager as a JSON string with exactly one field, `value`, containing the standard
base64 encoding. Plain volume creation does not generate a key.

For an existing disk, supply a reference to the **original** key already stored in that format
under the configured AWS prefix. If the original key is held elsewhere, import those exact
bytes through your customer-controlled secret-management process first. Do not print them or
put them in shell arguments. Selecting an existing secret validates its format and identity,
not whether it can decrypt the disk. Rotation must be disabled.

## Configure once per project

These helpers require a CLI build with `project storage configure`, `volume auto-unlock`,
and `volume create --auto-unlock`, in addition to the [lifecycle prerequisites](/containers/cli#prerequisites).
The published v0.18.9 CLI does not include them. Until a release contains them, use a build
supplied for the Containers rollout. Confirm `tinfoil project storage configure --help` and
`tinfoil volume auto-unlock configure --help` show those exact commands, and that
`tinfoil volume create --help` lists `--auto-unlock`.

With an [organization admin login](/containers/cli#authenticating), save the provider metadata:

```bash theme={"dark"}
tinfoil project storage configure owner/repo \
  --keyserver-url https://keys.example.com \
  --aws-region us-east-2 --aws-prefix tinfoil --aws-profile customer
```

Replace the example repository, endpoint, region, prefix, and AWS profile with your own.
An explicit `owner/repo` works before the first instance exists; an existing project ID also
works. The CLI verifies organization identity and repository access without creating an instance
or project. Repeating identical setup is safe; different saved custody metadata is refused.
With `--output json`, setup reports `configuration: PROFILE_SAVED` and
`policy: not_applied`; neither field means AWS access or remote approval was verified.

The helper requires an HTTPS **origin**, without a path, credentials, query, or fragment.
Configure your keyserver with `BACKEND=aws`, the same `AWS_REGION`, and
`AWS_SECRETS_PREFIX` matching `--aws-prefix`. Use `--aws-prefix=""` explicitly for no prefix.
The CLI and keyserver must access the same customer AWS account and region; policy resolves
secrets by name under that prefix, not by a cross-account ARN.
Omit `--aws-profile` to use the standard AWS credential chain. Vault and file provisioning
are not implemented by this helper.

You can save an exact domain with `--domain`; otherwise supply it during volume setup.
Missing required values prompt only in an interactive terminal. Scripts must supply them.

## Prepare a volume

Use your actual local `tinfoil-config.yml`, retaining its application image and other settings.
The example assumes a top-level volume named `data` already exists and is mounted by your
application. `--mount data` selects that name, not a filesystem path.

Choose a **new config release tag** to publish after reviewing the generated config;
`v1.2.3` below is an example, not a guest version. Supply the exact known instance domain
(or a verified custom domain you will use), not a guessed hostname. The helper's `--domain`
pins policy; it does not configure DNS or assign a domain to an instance.

### New disk: generate the key once

```bash theme={"dark"}
tinfoil volume create app-data --size 30GiB --host HOST_NAME --auto-unlock \
  --project owner/repo --mount data --tag v1.2.3 --domain app.example.com \
  --config-file tinfoil-config.yml --config-out prepared-config.yml \
  --policy-out prepared-policy.yml
```

This allocates a new disk, generates its key once, stores it in your AWS account, and prepares
local artifacts. It does **not** attach, deploy, or unlock the disk. Keep the returned volume
UUID for later attachment and recovery. With the example prefix, the AWS secret name is
`tinfoil/volumes/<UUID>/key`; the generated private reference is
`VOLUME_<UUID_WITH_UNDERSCORES>_KEY`.

### Existing disk: select only the original key

Do not run the new-disk command for an existing volume. Use its UUID and original secret:

```bash theme={"dark"}
tinfoil volume auto-unlock configure VOLUME_ID \
  --project owner/repo --mount data --existing-secret ORIGINAL_SECRET_NAME_OR_ARN \
  --tag v1.2.3 --domain app.example.com \
  --config-file tinfoil-config.yml --config-out prepared-config.yml \
  --policy-out prepared-policy.yml
```

`--existing-secret` is a name or full ARN, never the raw or base64-encoded 64-byte key. Do not
paste key bytes into command arguments: shell history and process arguments can expose them
before any CLI validation. The reference is required even when local metadata is missing.
The CLI rejects recognizable raw-key encodings before network requests or metadata writes;
this cannot undo exposure in shell history or process arguments.
This path reads the original secret; it does not generate or rotate a key.
The selected secret must be under the configured prefix. An ARN is resolved to its actual
name before the policy path is derived.

An imported secret must belong to this disk and storage scope. Do not reuse one tagged for
another volume or change its provenance tags to bypass a mismatch. Format validation alone
cannot establish that an untagged imported key is the original key for the disk.

### Review the artifacts

The helper adds a missing `keyserver-url` and the selected mount's `key-secret`, retaining an
existing disk's declared secret reference. It preserves other config fields and comments and
refuses conflicting values. The policy pins the repository, tag, and domain, and maps the
private reference to the AWS secret's path and `value` field. Key bytes are not in either file.

Pass `--policy-file policy.yml` to prepare an additive copy of an existing policy. Without
that input, `prepared-policy.yml` is a **fragment for manual review and merging**, not a
replacement for the keyserver's full policy. Include mappings for all private secrets the
release requests, not only the volume key.

Setting `keyserver-url` selects private delivery for all declared container, model, and volume
secrets in a non-debug instance. This helper provisions only the selected volume key. Store
the other required values in your customer secret store and map them in the reviewed policy;
missing values fail boot rather than falling back to Tinfoil-managed secrets.

Input and output paths must differ, output parent directories must exist, and new-volume
outputs must not already exist. Existing-volume setup can reuse identical outputs but refuses
to overwrite different contents. Choose fresh output filenames when preparing a new release.

## Keep approval explicit

The CLI prepares local configuration and policy artifacts. It does not remotely approve a
release, install or reload policy, deploy a keyserver, or observe a guest filesystem unlock.

Adding `keyserver-url` or `volumes[].key-secret` changes measured configuration. Review the
config, publish a **new measured release**, and authorize that exact repository, tag, and
domain on your keyserver before Deploy. Editing local YAML does not change an already
published release. Review and install the policy through your own deployment process, then
restart the keyserver so it loads the policy at startup.

Propose the prepared config, then review and merge the returned pull request:

```bash theme={"dark"}
tinfoil repo config pr owner/repo --file prepared-config.yml
```

Only after merging, publish the exact tag used in the policy:

```bash theme={"dark"}
tinfoil repo build run owner/repo --version v1.2.3
tinfoil repo build status owner/repo --version v1.2.3
tinfoil repo build info owner/repo
```

Wait for **both** release workflow phases and a published release for that tag; a queued
workflow or a Git tag alone is not deployable. Independently install and load the reviewed
policy on your keyserver. Then use the [normal create/attach/deploy workflow](/containers/encrypted-volumes#create-and-attach-a-volume)
with the returned disk UUID, selected mount, and exact approved repository, tag, and domain.

The reference keyserver permits only one workload entry per repository/tag pair, with one
exact domain. Merge new secret mappings into that entry only when its domain also matches.
Do not add duplicate repository/tag entries for different domains or broaden an existing
policy. Different instance domains need distinct released config tags or independently
operated keyservers.

For instances switching from managed secrets, explicitly
[clear their saved managed-secret selection](/containers/private-secrets#switching-an-existing-instance-to-private-delivery).
Never add a volume key to Tinfoil-managed secrets to satisfy a validation failure.

## Authorize another release without changing the volume key

Reuse the existing volume UUID and original secret ARN when preparing another application
release. First edit `./tinfoil-config.yml` to contain the **intended new release's changes**,
including its intended image. Preserve `keyserver-url`, the selected mount's name and
`key-secret`, and the application's persistent mount mapping. Merely assigning a new tag to
an unchanged v1 config does not update the application image.

Use a trusted local copy of the **currently installed full keyserver policy** as
`./installed-policy-v1.yml`. In this example, it contains the existing release's approval,
and `v2.0.0` is the new config release tag you intend to publish:

```bash theme={"dark"}
tinfoil volume auto-unlock configure VOLUME_ID \
  --project owner/repo --mount data \
  --existing-secret ORIGINAL_SECRET_ARN \
  --tag v2.0.0 --domain app.example.com \
  --config-file ./tinfoil-config.yml \
  --config-out ./prepared-config-v2.yml \
  --policy-file ./installed-policy-v1.yml \
  --policy-out ./prepared-policy-v2.yml
```

These output paths are distinct from the inputs and the previous release's outputs. The CLI
reads the original secret and retains its reference, AWS path, ARN, and version. It does not
create or rotate a secret, allocate another disk, or reformat the volume.

The candidate policy retains v1 and adds the exact v2 repository/tag/domain approval. An
already matching entry is reused. Otherwise, the CLI first tries `volume-<UUID>`; when that
name is occupied, it uses a deterministic suffix containing the full SHA-256 hash of the
repository/tag/domain tuple. A conflicting derived name is refused, never overwritten.
The name is not the authorization boundary: the exact pins still apply. Repeating preparation
with the same inputs produces byte-identical outputs without duplicate entries.

A new approval maps only the selected volume key. Review and add mappings for any other
private secrets the new release needs; they are not copied automatically from the old approval.
The [one-domain-per-repository/tag restriction](#keep-approval-explicit) still applies.

Review the generated config and open its pull request:

```bash theme={"dark"}
tinfoil repo config pr owner/repo --file ./prepared-config-v2.yml
```

After reviewing and merging that PR, publish the exact new tag:

```bash theme={"dark"}
tinfoil repo build run owner/repo --version v2.0.0
tinfoil repo build status owner/repo --version v2.0.0
tinfoil repo build info owner/repo
```

Wait for both release workflows and the published release. Separately approve and install
the reviewed merged `./prepared-policy-v2.yml` through your keyserver deployment process,
then restart the keyserver to load it. Only then [Update the instance](/containers/updates#updates-with-persistent-volumes)
to the new tag, retaining the same disk and original key. The CLI neither installs the policy
nor verifies that it was loaded; `CONFIGURED_LOCAL` is not remote approval or observed unlock.

**Adding v2 does not revoke v1.** The old release remains authorized until you explicitly remove
its policy entry and reload/restart the keyserver. Retain that approval through the rollback
window and while any instance may still need to boot or Deploy the old release. Retiring an
approval is a separate operator decision, not an automatic part of preparing v2. Do not delete
or rotate the volume key when retiring an approval.

## What each phase proves

```bash theme={"dark"}
tinfoil volume auto-unlock status VOLUME_ID --project owner/repo --output json
```

Status reads local evidence, not guest unlock state. Its `configuration` field can be:

* `CONFIGURED_LOCAL`: the saved metadata and prepared artifact hashes agree.
* `INCOMPLETE`: setup stopped before both artifacts were recorded; inspect the recovery phase.
* `STALE`: an artifact is missing or changed, or local metadata disagrees.
* `UNKNOWN`: this scope has no profile or receipt; it does not mean the disk is locked or its key is lost.

The receipt's `phase` records `allocated`, `secret_create_attempted`,
`verifying_existing_secret`, `secret_stored`, or `prepared`. These are setup checkpoints,
not remote deployment or unlock states. Failed secret verification marks reconfiguration
incomplete. Artifact conflicts preserve the prior receipt; they do not confirm that the new
release has been configured successfully.

The JSON `unlock` field is `not_observed`. Even `CONFIGURED_LOCAL` carries
`awaiting publication/policy load; unlock not observed`. The CLI does not confirm remote
publication or policy loading, so completing those steps does not turn this into an unlock check.

| Phase | Evidence | Not established |
| - | - | - |
| Project storage configured | Local provider and keyserver metadata saved for the selected account context and repository | AWS permissions, keyserver availability, or policy approval |
| Key stored or original key selected | A private secret reference is available for setup | That an existing key matches this disk's data |
| Config and policy prepared | Local artifacts describe the intended mount and pinned release | Publication, installation, or remote policy loading |
| Disk attached | The controlplane records an attachment to a named mount | Secret delivery or filesystem unlock |
| Instance Running | The lifecycle reached Running | An attested observation of the mounted filesystem by the CLI |

Confirm the workload's unlock result and that its durable-data path is backed by the expected
unlocked filesystem before relying on writes. Keyserver HTTP health alone is not unlock proof.

## Stop, Deploy, and recovery

Stop followed by Deploy retains the disk and its saved attachment. Reuse the actual disk,
the intended measured application release, and the original key. Do not substitute an example
image, create another disk, or generate a replacement key to recover data. Preserve a backup
of the original key outside the enclave, and keep the authorized private keyserver available.

For an Update, publish and authorize the new measured release while retaining the same disk
and key. [Updates with persistent volumes](/containers/updates#updates-with-persistent-volumes)
replace the running enclave and require downtime.

If setup fails after allocation, retain the reported volume UUID, secret reference, and phase.
Use the existing-disk command with that original reference and reviewed artifact flags; do not
retry by creating a replacement disk. Recovery checks the original secret's identity and
version and does not generate another key. If the first key never reached AWS, setup remains
incomplete and needs operator investigation. If allocation itself has an uncertain outcome,
inspect `tinfoil volume list` before retrying.

**Debug mode is not automatic private unlock.** It ignores `keyserver-url` and uses host-supplied
managed secrets. Never expose a production volume key or use a production disk in debug mode;
use disposable test disks and test secrets instead.

## Manual unlock is an advanced alternative

Omit `key-secret` only if your application implements the supported runtime unlock flow.
The application uses the volume's control socket at
`/run/tinfoil/volumes/<mount-name>/control.sock`; this is an application integration, not a
generic CLI unlock command. Verify the enclave before delivering the original key, and wait
for successful unlock before using the mounted path for durable writes.

Boot-time private-secret unlock is supported with `cvm-version` 0.14.4 or later. The
locked-volume write guard makes a locked
mount's placeholder **read-only**, but requires a newly published measured guest release and
an explicit update of the approved guest release/measurement pin. That release has not been
published; do not assume the 0.14.4 minimum includes the guard or rely on it for write protection.
Installing the CLI does not publish a guest image, deploy it, or update
runtime pins.

The guard prevents writes from silently landing in ephemeral storage at a locked volume path.
It does not make other application paths persistent. Files outside the unlocked mounted
filesystem remain ephemeral across Stop and Deploy or replacement during Update.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.