Project Attachments

How vault items, configs, registries, and secrets are attached to a project and used by containers.

Updated 22 Jun 20267 min read

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":

  1. Organisation → Project: an attachment.
  2. 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.

TypeWired into a container as
TLS Bundlefiles at a mount path (<path>/ca.crt, <path>/tls.crt, <path>/tls.key)
X.509 Certificatea single file at a mount path
GPG Keypairfiles at a mount path (public + private + optional passphrase)
SSH Keypairfiles at a mount path (public + private)
Encryption Keya file at a mount path
Registrythe registry field on the container — used to pull the image
Secretan environment variable — you name the variable explicitly
Env Filefiles at a mount path, also exposed as env vars
YAML Configa file at a mount path
JSON Configa file at a mount path
Plain Configa 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/app

At 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-key

A 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 handle

The 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 treats TERMINATING and TERMINATED containers 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:

RateWhen it accrues
BaseWhile the item exists in the organisation (regardless of attachment)
AttachedPer 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

ActionMinimum role
Create an item at the org levelProject member (varies per type)
Attach / detach to a projectAnyone with update permission on the project
Wire an attached item into a containerAnyone 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 TERMINATING or TERMINATED before the detach guard lifts. If you just clicked delete and the container is still showing RUNNING, give it a few seconds and retry.

See also