Skip to main content

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

FieldRequiredDescription
apiVersionYesMust be stacks.foundrydb.com/v1.
resourcesYesList of resource definitions. At least one required.
dependenciesNoMaps a resource symbolic name to the list of resource names that must reach Running before it is created. Defines provisioning order.
costPreview.inferenceBudgetUsdMonthlyNoMonthly 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 keyRequiredDescription
database_typeYespostgresql, mysql, mongodb, valkey, kafka, or opensearch.
versionYesEngine version string (e.g. "17" for PostgreSQL).
plan_nameYesCompute plan (e.g. "tier-2").
storage_size_gbNoData disk size in GB. 1–1024.
storage_tierNo"standard" or "maxiops".
extensionsNoList of database extensions to enable (e.g. ["pgvector", "postgis"]).

files

Provisions an object-storage bucket.

Spec keyRequiredDescription
quota_soft_gbNoSoft quota in GB. 1–1024.
quota_hard_gbNoHard quota in GB. 1–1024.

inference

Mints an EU-routed inference key against your organization's configured provider.

Spec keyRequiredDescription
eu_onlyNoBoolean. When true, requests are restricted to EU inference endpoints.
monthly_budget_usdNoPer-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 keyRequiredDescription
attachment_kindNoFirst-party companion app identifier. Mutually exclusive with image_ref.
image_refNoContainer image reference for a custom app. Not allowed in public templates.
container_portNoPort the container listens on. Not allowed in public templates.
plan_nameNoCompute plan.
storage_size_gbNoDisk size in GB. 1–1024.
storage_tierNo"standard" or "maxiops".
attached_toNo$ref pointing to a database or files resource whose credentials should be injected.
envNoCustom environment variables. Subject to env-shadow protection (see below).
authNoBasic auth config for the app's endpoint.
domainNoCustom 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_kindApplicationPrimary use case
open-webuiOpen WebUIAI chat interface over an inference resource and vector database
directusDirectusHeadless CMS and content API
metabaseMetabaseBusiness intelligence and SQL exploration
nocodbNocoDBNo-code spreadsheet over a database
hasuraHasuraInstant 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 when attached_to points to a database.
  • S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET: injected when attached_to points 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

Ruleprivateorg_sharedpublic
image_ref allowed on appYesYesNo
container_port allowed on appYesYesNo
env allowed on appYesYesNo
App must use attachment_kindNoNoYes
Storage size max 1024 GBYesYesYes
MDB_*, DATABASE_URL, S3_* env keys blockedYesYesYes

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.