Project Attachments
How vault items, configs, registries, and secrets are attached to a project and used by containers.
Every shareable resource on Bahriya — TLS bundles, certificates, keypairs, encryption keys, registries, secrets, and the four config types — lives at the organisation level, not on a single project. Before a container can use one of these, the resource has to be attached to that container's project. This article walks through the model end to end.
Two boundaries, on purpose
The platform draws two distinct lines between "I created this thing" and "this thing is running in my workload":
- Organisation → Project: an attachment.
- Project → Container: a wiring (mount path, env-var name, registry handle).
Both steps are explicit. A vault item is never reachable from a container unless you've done both. This is what stops a developer in one project from quietly mounting a TLS key that belongs to a different project's stack — and what makes "which projects depend on this credential?" a one-screen question.
┌─────────────────────────────────┐
│ Organisation │
│ │
│ tls-bundle [encrypted] │
│ gpg-keypair [encrypted] │ ← created here
│ db-password [encrypted] │
│ ghcr-deploy [encrypted] │
│ nginx-conf │
│ │
└─────────────────────────────────┘
↓ attach
┌─────────────────────────────────┐
│ Project │
│ │
│ ✓ tls-bundle │ ← attached items only
│ ✓ db-password │
│ │
└─────────────────────────────────┘
↓ wire
┌─────────────────────────────────┐
│ Container │
│ │
│ /etc/tls/tls.crt │ ← mounted at chosen path
│ DATABASE_PASSWORD=… │ ← injected as named env var
│ │
└─────────────────────────────────┘What can be attached
Eleven resource types share the attachment model. All of them follow the same lifecycle (versioned, rotated, rolled back) and the same attach surface — only the body of each type and the way it's wired into a container differ.
| Type | Wired into a container as |
|---|---|
| TLS Bundle | files at a mount path (<path>/ca.crt, <path>/tls.crt, <path>/tls.key) |
| X.509 Certificate | a single file at a mount path |
| GPG Keypair | files at a mount path (public + private + optional passphrase) |
| SSH Keypair | files at a mount path (public + private) |
| Encryption Key | a file at a mount path |
| Registry | the registry field on the container — used to pull the image |
| Secret | an environment variable — you name the variable explicitly |
| Env File | files at a mount path, also exposed as env vars |
| YAML Config | a file at a mount path |
| JSON Config | a file at a mount path |
| Plain Config | a file at a mount path |
Attaching an item to a project
You can attach from four surfaces — they all hit the same endpoint and produce the same result.
Console UI
On the Projects listing page, click the paperclip icon on a project row. The side panel that opens groups every type as an accordion. Click a type, pick the item from its dropdown, and the attachment is created.
To detach, open the same panel and click the trash icon next to the row.
Reis CLI
# Attach
reis project:attach <project-id> tls_bundles edge-cert
reis project:attach <project-id> registries ghcr-deploy
reis project:attach <project-id> secrets db-password
# Detach
reis project:detach <project-id> tls_bundles edge-cert
# List everything attached to a project, grouped by type
reis project:attachments <project-id>The CLI accepts both the plural type (tls_bundles) and the friendlier singular alias (tls_bundle).
Terraform
Each type has a dedicated attachment resource. Both project_id and handle force replacement — to change either, Terraform tears down the old attachment and creates a new one.
resource "bahriya_project_tls_bundle_attachment" "edge" {
project_id = bahriya_project.app.id
handle = bahriya_tls_bundle.edge.handle
}
resource "bahriya_project_registry_attachment" "deploy" {
project_id = bahriya_project.app.id
handle = bahriya_registry.ghcr_deploy.handle
}See the Terraform Provider — Projects article for the complete list of attachment resources.
API
POST /organisations/{org}/projects/{pid}/attach/{type}/{handle}
DELETE /organisations/{org}/projects/{pid}/attach/{type}/{handle}
GET /organisations/{org}/projects/{pid}/attachments{type} is the plural form (tls_bundles, registries, etc).
Wiring an attached item into a container
Once an item is attached to a project, the container references it by handle, not by ID. Each type has its own wiring shape — handle plus whatever additional knob the type needs.
Mounted as files
kind: container
type: http
handle: my-api
project: my-project
image: my-org/my-api:1.0
regions: [falkenstein-1]
vault:
tls_bundles:
- handle: edge-cert
mount_path: /etc/tls
configs:
yaml_configs:
- handle: app-config
mount_path: /etc/appAt deploy time the platform materialises the decrypted material at the path you chose. Your application reads files from disk like it would in any other environment — no SDK, no extra network call.
Injected as environment variables
Secrets wire one-to-one with environment-variable names. You choose the variable name; the platform supplies the decrypted value at deploy time.
workload:
secrets:
- envvar: DATABASE_PASSWORD
secret: db-password
- envvar: STRIPE_KEY
secret: stripe-live-keyA single secret can be wired into the same container under more than one variable name if your app needs that — and a single secret can be wired into many containers in the project.
Used to pull the container image
Registries are referenced by a single field at the top of the container body:
kind: container
project: my-project
image: ghcr.io/my-org/my-api:1.0
registry: ghcr-deploy # ← attached registry's handleThe platform authenticates with those credentials when pulling the image at deploy time.
Detaching and the in-use guard
You can detach an attached item at any moment except if a running container in the same project still wires it. The API refuses the detach with a clear error naming the blocking container(s):
Cannot detach tls_bundles 'edge-cert' from project: container 'my-api' still references it. Detach from the container first or update the container to remove the reference.
Two ways forward when this hits:
- Update the container to remove the wiring (edit its YAML or the corresponding UI form, redeploy), then retry the detach.
- Tear down the container with
container:delete(or the trash icon in the console). The guard treatsTERMINATINGandTERMINATEDcontainers as no longer using the item, so the detach succeeds as soon as the deletion is in flight.
Rotation and rollback
Rotation never changes attachment state — it creates a new version of the underlying item. Containers that mount the item pick up the new version on their next deploy. If a rotation breaks something, activate the previous version (reis tls_bundle:activate_version <id> 1) and redeploy. The attachment is untouched throughout.
Billing
Every attachable type is billed against two rates:
| Rate | When it accrues |
|---|---|
| Base | While the item exists in the organisation (regardless of attachment) |
| Attached | Per project per region per month, while the item is attached to a project |
So a TLS bundle that exists but isn't attached costs only its base rate. Attaching it to one project across two regions adds 2× the attached rate per month. Detach it and the attached-rate clock stops; the base rate continues until you delete the item.
See Pricing → Vault & Configs pricing for current per-type figures.
Permissions
| Action | Minimum role |
|---|---|
| Create an item at the org level | Project member (varies per type) |
| Attach / detach to a project | Anyone with update permission on the project |
| Wire an attached item into a container | Anyone with update permission on the container |
If you can edit a project, you can attach to and detach from it. If you can edit a container, you can wire and unwire items the project has already attached.
Quick sanity checks
A few questions worth asking when something doesn't behave the way you expect:
- Container can't find the secret / config? Confirm the item is attached to the container's project — the project-attachments panel on the projects listing is the fastest place to look. A container can't wire what isn't attached.
- Attached item not showing up in container forms? Re-check the project selector at the top of the form — the available pickers only show items attached to the currently selected project.
- Detach 409 even after deleting the container? The container's status has to be
TERMINATINGorTERMINATEDbefore the detach guard lifts. If you just clicked delete and the container is still showingRUNNING, give it a few seconds and retry.