Authoring Templates
You can author your own stack templates to reproduce a multi-resource setup on demand. A template is a versioned descriptor that declares which platform primitives to compose and how they connect. A template starts private to your organization, and you can later share it with colleagues or publish it to the platform marketplace.
Template structure
A template has two parts: a metadata envelope and a descriptor that declares the resources the stack composes.
curl -X POST https://api.foundrydb.com/stacks/templates \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-cms",
"display_name": "My CMS",
"description": "A PostgreSQL-backed Directus installation.",
"version": "1.0.0",
"visibility": "private",
"descriptor": {
"apiVersion": "stacks.foundrydb.com/v1",
"resources": [
{
"name": "db",
"kind": "database",
"spec": {
"database_type": "postgresql",
"version": "17",
"plan_name": "tier-2",
"storage_size_gb": 50,
"storage_tier": "maxiops"
}
},
{
"name": "app",
"kind": "app",
"spec": {
"attachment_kind": "directus",
"attached_to": "$db"
}
}
],
"dependencies": {
"app": ["db"]
}
}
}'
The template starts in draft publication status and is only visible to your organization.
Descriptor fields
| Field | Required | Description |
|---|---|---|
apiVersion | Yes | Must be stacks.foundrydb.com/v1. |
resources | Yes | List of resource definitions. At least one required. |
dependencies | No | Maps a resource symbolic name to the list of resource names that must reach Running before it is created. Defines provisioning order. |
costPreview.inferenceBudgetUsdMonthly | No | Monthly spend ceiling for any inference key the stack mints. Shown as an is_ceiling line item in cost previews. |
Resource kinds
Each resource in resources has a name (symbolic, used in $ref tokens and dependencies), a kind, and a spec.
database
Provisions a managed database service.
| Spec key | Required | Description |
|---|---|---|
database_type | Yes | postgresql, mysql, mongodb, valkey, kafka, or opensearch. |
version | Yes | Engine version string (e.g. "17" for PostgreSQL). |
plan_name | Yes | Compute plan (e.g. "tier-2"). |
storage_size_gb | No | Data disk size in GB. 1–1024. |
storage_tier | No | "standard" or "maxiops". |
extensions | No | List of database extensions to enable (e.g. ["pgvector", "postgis"]). |
files
Provisions an object-storage bucket.
| Spec key | Required | Description |
|---|---|---|
quota_soft_gb | No | Soft quota in GB. 1–1024. |
quota_hard_gb | No | Hard quota in GB. 1–1024. |
inference
Mints an EU-routed inference key against your organization's configured provider.
| Spec key | Required | Description |
|---|---|---|
eu_only | No | Boolean. When true, requests are restricted to EU inference endpoints. |
monthly_budget_usd | No | Per-month spend ceiling in USD. Shown as is_ceiling: true in cost previews. |
app
Deploys a hosted application. The platform wires database credentials and bucket credentials into the app's environment automatically via attached_to references.
Trusted (private and org-shared) templates:
| Spec key | Required | Description |
|---|---|---|
attachment_kind | No | First-party companion app identifier. Mutually exclusive with image_ref. |
image_ref | No | Container image reference for a custom app. Not allowed in public templates. |
container_port | No | Port the container listens on. Not allowed in public templates. |
plan_name | No | Compute plan. |
storage_size_gb | No | Disk size in GB. 1–1024. |
storage_tier | No | "standard" or "maxiops". |
attached_to | No | $ref pointing to a database or files resource whose credentials should be injected. |
env | No | Custom environment variables. Subject to env-shadow protection (see below). |
auth | No | Basic auth config for the app's endpoint. |
domain | No | Custom domain for the app. |
First-party companion apps. The following attachment_kind values map to pre-built, platform-vetted images. Using one of these avoids the need to specify image_ref or container_port, and it is the only option available in public templates:
attachment_kind | Application | Primary use case |
|---|---|---|
open-webui | Open WebUI | AI chat interface over an inference resource and vector database |
directus | Directus | Headless CMS and content API |
metabase | Metabase | Business intelligence and SQL exploration |
nocodb | NocoDB | No-code spreadsheet over a database |
hasura | Hasura | Instant GraphQL API over a PostgreSQL database |
When attachment_kind is used, the platform automatically provisions the companion image with the correct port and injects the required credentials. You do not need to set image_ref or container_port.
Public templates have a narrower allow-list. The image_ref, container_port, and env keys are not available. A public template's app must use a first-party companion app via attachment_kind. This restriction exists because public templates run under any organization's credentials: arbitrary images and environment overrides would create an unacceptable supply-chain risk for users who launch from the marketplace.
Cross-resource references
Use $ref tokens in spec values to wire one resource's output into another's configuration. The reconciler resolves them after the upstream resource reaches Running.
{
"name": "app",
"kind": "app",
"spec": {
"attachment_kind": "open-webui",
"attached_to": "$db"
}
}
The platform injects the appropriate connection variables (see Env-shadow protection) based on the upstream resource kind.
Env-shadow protection
When your app resource includes an env map, the following keys are reserved by the platform and may not be set by a template author:
DATABASE_URL: injected whenattached_topoints to a database.S3_ENDPOINT,S3_BUCKET,S3_ACCESS_KEY,S3_SECRET: injected whenattached_topoints to a files resource.- Any key starting with
MDB_: reserved for platform-injected connection metadata.
Attempting to set any of these keys in env will fail validation with a descriptive error. The restriction applies at template save, at publish, and again at every launch as a defense in depth.
OPENAI_* keys are not restricted and are intentionally available for wiring the minted inference key into your application.
Storage sizing bounds
Any storage_size_gb, quota_soft_gb, or quota_hard_gb value must be between 1 and 1024 GB. Values outside this range are rejected at save time.
Dependency ordering
dependencies is a map from resource symbolic name to a list of resource names that must reach Running before it is created. Resources with no entries in dependencies are created in parallel.
{
"dependencies": {
"app": ["db", "files"],
"inference": []
}
}
In this example, db, files, and inference are created in parallel. app is created only after both db and files are Running.
Dependency ordering matters most when your app resource needs an already-configured database at startup. Without a declared dependency, the app might receive DATABASE_URL via env injection before the database is reachable, causing startup failures. Declare the dependency and the reconciler enforces the correct sequence.
Cycles in dependencies are rejected at save time. An entry that names a resource not declared in resources is also rejected.
Editing a template
A template in draft, rejected, or unpublished status can be updated:
curl -X PATCH https://api.foundrydb.com/stacks/templates/$TEMPLATE_ID \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description.",
"version": "1.1.0",
"descriptor": { ... }
}'
On PATCH, only supplied fields are updated. A published or in-review template is immutable: create a new template (with a new name and version) to replace it.
Deleting a template
curl -X DELETE https://api.foundrydb.com/stacks/templates/$TEMPLATE_ID \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN"
Returns 204 No Content. Stacks already launched from this template keep running on their own snapshotted descriptor. Deletion prevents new launches from this template only.
Validation rules by visibility
| Rule | private | org_shared | public |
|---|---|---|---|
image_ref allowed on app | Yes | Yes | No |
container_port allowed on app | Yes | Yes | No |
env allowed on app | Yes | Yes | No |
App must use attachment_kind | No | No | Yes |
| Storage size max 1024 GB | Yes | Yes | Yes |
MDB_*, DATABASE_URL, S3_* env keys blocked | Yes | Yes | Yes |
Validation runs at save, at publish, and at every launch. This three-layer approach means a template cannot loosen past a rule just by avoiding a re-save: every launch re-evaluates the current ruleset. A template that passed validation when it was created but no longer satisfies a tightened rule will fail at launch with a descriptive error explaining which rule it violates.
Next steps
Once your template is ready, see Publishing and the Marketplace to share it with your organization or the wider platform.