Tinfoil offers two ways to persist data. Encrypted volumes (this page) are block storage:
a filesystem attached to one enclave at a time, suited to large working sets such as build
trees, datasets, and fine-tuning checkpoints, encrypted under a secret the deployment owner
holds. The buckets sidecar is object storage: encrypted
objects in S3, reachable from any number of enclaves and clients, encrypted under keys your
end users hold.
Overview
A secure enclave encrypts its memory with a key that only the enclave’s own hardware can use. That is what guarantees no operator, including Tinfoil, can read the data being processed inside it. Memory, however, is not persistent, and even if it were, the key protecting it lives in hardware registers that cannot be read out or saved. Memory and the enclave’s ephemeral filesystem are lost when it stops; data written to an unlocked persistent volume is retained. Encrypted persistent volumes extend the enclave model to storage. They compose two primitives: private secret delivery and authenticated disk encryption. A volume’s contents are unreadable to anyone who does not hold its volume secret, and that secret never exists outside your infrastructure and the enclave.Using managed volumes
A managed volume is a disk owned by your organization. It lives on one host, attaches to one container instance at a time, and survives container deletion. Tinfoil manages the disk’s allocation and attachment; you manage its encryption secret. The volume (disk), its named volume mount, and its mount path are separate:
Declaring a mount does not create a disk. A mount requires an attached volume before deploy
only when it declares
key-secret (JSON key_secret). Mounts without it are optional and may
remain empty; without an attached disk, they provide no persistent storage.
Every container subscription includes a storage quota for volumes; the
Volumes tab in the dashboard shows allocated and available storage. Contact us to raise the quota.
The API’s volume_slots field lists declared mounts, and a volume’s slot field contains
its assigned mount name. Neither field is a filesystem path.
Before deploying with storage
Choose an unlock method before creating the instance. Allocating or attaching a disk does not supply its encryption secret, unlock its filesystem, or make the mount writable.- Automatic unlock: generate and durably store the volume secret in infrastructure you control, run a reachable keyserver, set
keyserver-url, and declare the secret withkey-secret. Authorize the exact config repository, release tag, and instance domain in the keyserver policy before boot. The keyserver must release that secret after attestation on every boot; declaring its name is not enough. - Manual unlock: omit
key-secretand use a workload that implements a supported runtime unlock flow. Verify the enclave before delivering the original volume secret through that flow. There is no generictinfoil volume unlockcommand. If your application has no unlock flow, use automatic unlock instead.
keyserver-url delivery, volumes[].key-secret declares an
external secret; its name and value do not need to exist in Tinfoil’s managed
secret store. Never upload the volume key there to satisfy validation. An
existing instance with managed secrets must explicitly clear its saved selection
when switching to private delivery. Required disk attachments are still checked,
but a successful update plan does not verify keyserver authorization, key release,
or disk unlock. Debug mode ignores the keyserver and is not a confidential way
to access a production volume.
Create and attach a volume
- Prepare an unlock method, declare a named volume mount in your repository’s
tinfoil-config.yml, and publish a release. Authorize that release before deploying if you use a keyserver. - Create a disk on the host you will use, then create the instance with
--volumeso it attaches and deploys:
dev-disk is the managed volume. workspace is the config’s volumes[].name. Omit
:workspace when the config declares exactly one mount. Create uses the disk’s host; an
explicit --host must match. The disk is never created implicitly. Repeat --volume to
attach several disks, and specify each mount name. Pass a disk for every required mount;
optional mounts may remain empty. If required mounts exist and you omit --volume, the CLI
refuses before creating the instance and prints commands to create and attach the disks.
In the dashboard’s create form, select an existing disk or create one for each required
mount. Optional mounts do not require a disk. All selected disks must be on the instance’s
host. Creation attaches the selected disks and then deploys the instance.
To swap disks later, stop the instance and wait until it is Stopped, then:
--as MOUNT_NAME selects the mount and defaults to the only declared mount when omitted. tinfoil container deploy my-app --volume other-disk:workspace
also attaches a detached disk before deploying. The container must already be on that disk’s host.
Once attached, later deploys and updates reuse the disk; omit --volume.
You can also create, attach, and deploy from the dashboard: Containers → Volumes, then
Create volume on the instance’s host, Attach it to the workspace mount, and deploy
the instance. Reuse an unattached volume on that host instead of creating one.
With key-secret, your keyserver supplies the volume secret at boot only when its release
policy authorizes the enclave. For manual unlock, use your workload’s documented flow after
verifying the enclave; the attach command does not unlock the disk.
Write durable data only after confirming that the unlocked filesystem is mounted at the
configured path. Files written elsewhere in the enclave’s ephemeral filesystem do not survive
Stop followed by Deploy. Attaching a disk does not move existing files onto it.
Reusing a volume requires its original encryption secret. Back up that secret in infrastructure
you control before relying on the disk. Stop followed by Deploy keeps the saved disk attachment;
use the same disk and original key, not a newly created replacement. Unattended unlock also
requires your keyserver to be reachable and to authorize the deployed release on every boot.
Volume lifecycle
Stop the container before attaching or detaching a volume. If its previous enclave is still
shutting down, wait for it to release the disk and retry. Volume assignments cannot change
while an update is in progress. Detach a volume before removing its mount from the config or
moving the container to another host; the disk remains on its original host.
Volume sizes are fixed at creation. Rename changes only the disk’s display name, not its
mount name or contents. Stopped and unattached volumes still count toward your storage allocation;
capacity is released only after volume deletion completes.
Key management
Persistent storage needs a long-term secret to encrypt and authenticate data under. That secret cannot be managed by Tinfoil, since anyone holding it can read or modify the data. It is created and kept on your side. A volume uses a random 64-byte volume secret, stored durably in infrastructure you control. Plaintinfoil volume create allocates an empty disk; it does not generate, store,
or deliver an encryption key. The enclave never generates or persists this secret.
With explicit new-volume auto-unlock mode,
the CLI generates the key once and stores it in your AWS Secrets Manager account. Existing
volumes must use their original key; generation is not the default behavior of volume creation.
On every boot, the volume secret is delivered to the enclave after attestation, over a connection that
terminates inside the enclave. There are two ways to do this:
- Automatically, by declaring the volume secret as a private secret and letting your keyserver release it once the enclave’s attestation matches your policy.
- Manually, by sending the secret over the attested connection to the running enclave, which unlocks the volume at runtime.
Volume keys are not ordinary rotating secrets
An existing encrypted volume must keep its original key. Do not generate a replacement key when enabling automatic unlock, recovering a failed setup, or moving the same disk to another instance. Import the original key into your private secret store instead. Replacing the secret value, enabling automatic secret rotation, or changingkey-secret does
not re-encrypt the disk. A different key cannot unlock the existing data. Key rotation requires
an explicit data migration or supported re-encryption procedure, with the original key and a
recoverable backup retained until the migrated data has been verified. Do not use ordinary
private-secret rotation as a volume-key rotation procedure.
Disk encryption
Once the enclave holds the derived keys, it opens the volume with two Linux kernel primitives,dm-crypt and dm-integrity, configured for authenticated encryption:
- Confidentiality: AES-XTS with a fresh random IV for every write, so identical plaintext produces different ciphertext each time it is written, regardless of where it lands on disk.
- Integrity: an HMAC-SHA256 tag over every 4 KiB sector. The tag and the IV are stored in
a journaled
dm-integritylayer beneath the encrypted device, and any sector whose tag does not verify is rejected on read.
Threat model
The goal is to give the volume the same guarantees as the enclave’s encrypted memory, and to be explicit about what it does not cover. Protected. An attacker with full access to the disk cannot read its contents, and cannot forge or alter a sector’s contents without the enclave detecting it. Because every write uses a random IV, the attacker also cannot tell whether two sectors hold the same data or whether a sector was rewritten with its previous contents, which rules out ciphertext-comparison attacks. Not protected: rollback. Integrity protection guarantees the ciphertext has not been tampered with, but an attacker who replays an earlier, valid version of a sector, or restores the whole disk to an earlier state, is not detected. This mirrors the memory model, where the hardware protects against modification but not replay. Not protected: access patterns. A privileged attacker can observe which sectors the enclave reads and writes, and when. This side channel can leak information about enclave activity even though the contents are encrypted. It can be closed at the application level by running an Oblivious RAM (ORAM) scheme on top of the volume, at a performance cost that depends on your workload. For an example of such implementation and a detailed study of this tradeoff, see Opal, a private memory system for personal AI built on Tinfoil.Configuration
Declare named volume mounts at the top level oftinfoil-config.yml and reference them in application containers by name.
Create and attach their backing disks separately.
key-secret names the private secret that carries the volume secret; omit it to unlock the
volume manually at runtime instead. A mount with key-secret requires a disk before deploy;
one without it may remain empty. The secret must be a private secret released by your keyserver. A
Tinfoil-managed secret would give Tinfoil the ability to read the volume.
tinfoil-config.yml
Docker’s writable layers, pulled images, and ordinary Docker volumes live in memory and
are lost when the enclave stops. Only data written through the unlocked persistent
filesystem at its configured mount path is durable on the volume.

