YAML mode
Manage Bahriya infrastructure declaratively with Reis YAML mode: file format, multi-document layout, create-or-update semantics, unknown fields, dry runs and exports.
Reis supports declarative infrastructure management through YAML files. Define your resources in YAML, commit them to version control, and apply them with reis apply -f for repeatable, auditable deployments.
This article is the orientation map — the file format, multi-document layout, and per-asset links. For the field-by-field reference of each asset type, jump straight to its dedicated page below.
Per-asset deployment guides
Each deployable asset has its own YAML walkthrough with the full set of fields that apply to it:
- Deploy an HTTP container
- Deploy a worker
- Deploy a cron job
- Deploy a Memcached instance
- Deploy a Valkey instance
- Deploy a MySQL instance (flag mode)
- Deploy a TLS bundle
- Deploy an X.509 certificate
- Deploy a GPG keypair
- Deploy an SSH keypair
- Deploy an encryption key
- Deploy a YAML config
- Deploy a JSON config
- Deploy a plain config
- Deploy an env file
- Manage a network policy
- Add HTTP container path rules
- Manage custom roles
- Manage billing details
Projects, registries, and secrets are simpler and covered inline at the end of this article.
Prerequisites
Before applying resources, set an organisation context. Reis needs to know which organisation to apply resources to:
# Set your default organisation (interactive)
reis org:switch
# Or pass it explicitly on each apply
reis apply -f infrastructure.yml --org 550e8400-e29b-41d4-a716-446655440000See Authentication for setting up credentials and your default organisation.
File format
Every YAML resource document carries a kind, an identity, and then the resource-specific fields. Fields are organised into named groups under top-level keys like workload, scaling, and networking:
kind: <resource-type>
project: <project-handle> # required for project-scoped resources
handle: <resource-handle>
name: <display-name>
regions:
- <region-id>
# resource-specific groups follow
workload:
cpu: "500"Supported kinds
| Kind | Per-asset guide |
|---|---|
container (type http) | HTTP container |
container (type worker) | Worker |
container (type cronjob) | Cron job |
memcached | Memcached |
valkey | Valkey |
mysql | MySQL |
tls_bundle | TLS bundle |
x509_cert | X.509 certificate |
gpg_keypair | GPG keypair |
ssh_keypair | SSH keypair |
encryption_key | Encryption key |
yaml_config | YAML config |
json_config | JSON config |
plain_config | Plain config |
env_file | Env file |
network_policy | Network policy |
pathrule | HTTP container path rule |
role | Custom role |
billing_details | Billing details |
registry | inline below |
secret | inline below |
project | inline below |
Multi-document files
Combine multiple resources in a single file using the YAML document separator ---:
kind: secret
handle: db-password
name: Database Password
value: s3cret-value-here
---
kind: registry
handle: ghcr
name: GitHub Registry
server: ghcr.io
username: myuser
password: ghp_xxxxxxxxxxxx
---
kind: container
project: my-project
handle: web-api
name: Web API
type: http
regions:
- falkenstein-1
- virginia-1
workload:
image: ghcr.io/myorg/api:v1.0.0
registry: ghcr
cpu: "500"
memory: "512"
port: 8080
healthcheck: /healthz
env:
NODE_ENV: production
secrets:
DB_PASSWORD: db-password
networking:
external: trueResources are applied in the order they appear. Define secrets and registries before the containers that reference them. Secrets and registries belong to the organisation rather than to a project: attach them to a project with reis project:attach before a container in that project uses them.
Container group structure
Every container — HTTP, worker, or cron job — uses the same group structure. Per-asset articles show which groups apply to which type.
| Group | What it controls |
|---|---|
| (top level) | name, handle, project, regions, type — identity and placement; init_jobs — init jobs that run before the main container starts |
workload | Image and registry, CPU/memory, port, health check, env vars, secrets, ENTRYPOINT/CMD overrides |
scaling | Manual replicas, autoscaling caps and targets |
storage | Ephemeral storage size, persistent volumes |
networking | Hostnames, DNS, external networking |
observability | Prometheus scraping |
security | Basic auth, rate limiting, IP allow/deny lists |
proxy_cache | Managed proxy cache settings |
schedule | Cron expression, timezone, retry limits (cron jobs only) |
vault | Mounted TLS bundles, certificates, keypairs and encryption keys |
configs | Env files and mounted YAML, JSON and plain-text configs |
Every container document states its type: http, worker or cronjob. reis apply refuses a container document without one.
Reis 1.4.0 and 1.4.1 grouped init jobs under lifecycle (lifecycle.init_jobs). They are now top-level init_jobs, because Terraform reserves the name lifecycle. From Reis 1.6, lifecycle is an unknown field: move the list to init_jobs.
Project fields
kind: project
handle: my-project
name: My Project
regions:
- falkenstein-1
- virginia-1A project is the namespace inside an organisation that owns containers, registries, secrets, vault items, configs, and Memcached instances. Members and roles are managed via the console or the API directly — they're not editable from YAML.
Attachments (which org-level registry, secret, TLS bundle, config, etc. are usable inside the project) are not managed from the project YAML. Use the dedicated project:attach, project:detach, and project:attachments commands instead — see Commands Reference: Projects.
Project quotas (read-only display)
When you reis export an existing project, you will also see a quotas: block showing the quotas applied to the project. These values are display-only: Reis ignores edits under quotas: when you re-apply the file, so an exported file applies without manual cleanup.
kind: project
handle: my-project
name: My Project
regions: [falkenstein-1, virginia-1]
quotas: # read-only — set by the platform
request:
cpu: 1000m
memory: 1G
limit:
cpu: 2000m
memory: 2GRegistry fields
kind: registry
handle: ghcr
name: GitHub Registry
server: ghcr.io
username: myuser
password: ghp_xxxxxxxxxxxxRegistries hold the credentials used to pull private images. Reference one from a container with workload.registry: <handle>.
Secret fields
kind: secret
handle: db-password
name: Database Password
value: s3cret-value-hereSecrets are encrypted at rest and injected into containers as environment variables — see the HTTP container guide for the workload.secrets mapping shape.
Create-or-update semantics
reis apply -f looks up each resource by its handle:
- If the resource does not exist, it is created.
- If the resource already exists, it is updated to match the YAML file. A field the file leaves out returns to its default, exactly as it would in Terraform. A field with no default, such as the image, keeps its current value.
That makes reis apply idempotent — running it multiple times with the same file produces the same result. Keep each file complete: state every field you rely on.
Applying a file that would clear or reset values
If an update would empty something that holds values today, such as environment variables, secrets, volumes, attached configs or an IP list, or would return a field you set back to its default because the file leaves it out, such as a port or an autoscaling setting, Reis lists those fields and asks before applying:
The file leaves out fields of container 'web-api' that hold values today. Applying it would change: workload.env, workload.port ('3000' → '8080')
Apply these changes? (yes/no) [no]:- In scripts and CI, pass
--yesto confirm. Without it, a non-interactive run leaves the resource unchanged and exits with an error. - With
--output json, the result for each resource lists the emptied fields asclearedand the fields returned to their default asreset, each with its current and default value. - To keep a value, state it in the file. A field the file states is never asked about, even when it is set to its default.
Projects, registries and other resources referenced by handle are resolved both when a resource is created and when it is updated.
Fields Reis does not know
A key that is not a field of the resource is usually a misspelling, and the field it was meant to set would quietly take its default. reis apply therefore refuses a document with an unknown field and names it:
`workload.bootstrap_tme` is not a field of container. Check the spelling: a misspelt field is ignored, and the field it meant takes its default. Fix or remove it, or pass --allow-unknown to ignore it.Pass --allow-unknown to apply the document anyway: the unknown fields are ignored, with a warning for each. The keys under env and secrets are your own variable names, so any name is accepted there.
When a resource is not applied
Resources in a file are applied in order, and resources applied before a failure stay applied. When a resource fails, Reis says which resources were applied and which were not, and exits with an error. A failed lookup of an existing resource stops that resource rather than creating a second one.
Dry run
Check a YAML file without making any changes:
reis apply -f infrastructure.yml --dry-runA dry run needs no login, organisation or network connection. It checks every document in the file, as a real apply does before it changes anything:
- Errors, which a real apply refuses: unknown fields, a container document without
type, and values that cannot be used. - Warnings, which a real apply reports and then applies: fields that a container type does not use or requires, and deprecated fields.
- Notes, for information: fields that a new resource needs, such as
workload.cpuandworkload.memoryfor a container. A dry run cannot tell whether the resource already exists, and an update keeps the current values of fields the file leaves out.
The dry run exits with an error when it finds any error or warning, so it can gate a CI pipeline. Unknown fields ignored with --allow-unknown do not fail it. With --output json, it lists the findings for each document.
A dry run does not check what only the platform can: that referenced projects, registries and secrets exist, and that the resources fit your limits. Those are checked when you apply.
Exporting resources
Export an existing resource to YAML format, by its ID:
# Export to stdout
reis export container 0d9b3c2e-5f1a-4c8e-9a6b-2f7e1d4c8a10
# Export to file
reis export container 0d9b3c2e-5f1a-4c8e-9a6b-2f7e1d4c8a10 --file web-api.ymlFind a resource's ID with the matching list command, for example reis container:list. Reis can export projects, containers, Memcached, Valkey and MySQL instances, registries and secrets.
The exported YAML uses the same grouped format that reis apply expects, the same one the console's YAML view shows, so you can export, modify, and re-apply. Files exported by Reis versions before 1.4, which list every field under its API name at the top level, still apply.
Tips
- Order matters in multi-document files. Define secrets and registries before the containers that reference them.
- Use handles, not UUIDs. Handles are immutable in Bahriya and are the primary identifier in YAML files.
- Environment variables can be specified as a YAML map (
KEY: value). Reis converts them to the API format automatically. - Commit your YAML files to version control for an audit trail of infrastructure changes.
- Use dry run to check files before applying them, especially in CI/CD pipelines.
- Write numbers as numbers (
port: 8080). Quoted numbers are accepted too, but exports write them unquoted.
YAML mode vs flag mode
| You're doing… | Reach for… |
|---|---|
| Managing a stack of related resources together | YAML mode |
| Onboarding a new project under version control | YAML mode |
| Restoring a previously exported config | YAML mode |
| A one-off deploy from your laptop | flag mode |
| A scripted CI job that builds the same container | flag mode |
| Quick edits to a field or two | flag mode |
Both modes produce identical results.