Skip to main content
A template with more than one service creates a stack: an application and the managed databases it needs, created together, connected and managed as a group. In this tutorial you write a template that lets the user choose Postgres or SQLite, optionally turn on a Redis cache, and wires the app to each database with placeholders and TLS client certificates, with no connection string typed by anyone. Each database has its own public address and port, and the app connects with a password plus a client certificate that the platform prepares before the first boot.

Before you start

You finished the first code template tutorial or know the basics of the manifest and the CLI.
A plan with room for the app and the databases. This template needs at least 512 MB for the app, 1,024 MB for Postgres and 512 MB for Redis, so 2,048 MB in the biggest case. The memory of every service comes out of the plan total, see Plans and limits.
Only the owner of a workspace can deploy a template into it. A member sees the templates and uses the stacks that already exist.

Step by step

1

Decide what each answer changes

Write down the questions first and what each answer switches on. This table is the design of the manifest:There is no expression language in the manifest. You get two tools: sets on an option, which flips several things at once, and when, which is one equality on one earlier question. Use sets when one choice changes more than one service or variable, as storage does, and when for a single yes/no.
2

Write the code that reads the environment

The app never builds an address: it reads variables and the certificate files that the manifest points to. Create src/storage.js (the rest of the app is yours):
src/storage.js
Add pg, ioredis and better-sqlite3 to src/package.json, and a small src/server.js that listens on PORT and answers /healthz. Because a database that is off leaves its variables empty (the platform does not write them), the code can tell which services exist by checking the variable.
3

Write the manifest

vertracloud.template.yaml
Read it from top to bottom:
  • sets on the options. services: { db: "off" } removes the database for the SQLite choice; env: { app: ... } adds variables to the service whose id is app. The last option chosen wins when several set the same thing, and sets can also add memory with ram_extra.
  • when on a question. cache_ttl is only asked, and only validated, when cache is true. A when can only point to a question that comes before it, and equals must match the type of that question (true for a boolean, one of the option values for a choice).
  • when on a service. The cache service is only created when cache is true. db has no when because the sets of storage already controls it: a service accepts only one condition, so pick one mechanism per service.
  • Placeholders. {{services.db.host}} and the others read from the service whose id is db. A placeholder that points to a service that is off resolves to empty and the variable is not written, so the app never sees a half-filled configuration.
4

Know what the platform fills in

For a database service, these placeholders are available:The certificate files live in /app/.vertra/certs/<database_id>/, written by the platform before the app’s first boot and kept in sync when you reset a password or a certificate. Databases on the platform always require TLS and a client certificate; a password alone never connects, which is why the manifest passes the three paths. The placeholders of an app service are {{services.<id>.url}} and .domain, and {{app.url}} and {{app.domain}} give the address of the service that is reading them. See Connect the app to the stack’s databases for the same information from the user’s side, with code for other languages and drivers.
An output can use placeholders too, but {{secret:<n>}} is not allowed there. To show a generated secret once, declare it in secrets and print {{secrets.<name>}} with secret: true and once: true.
5

Validate

Try breaking it on purpose to learn the messages. Rename the db service id in one place only and you get unknown_service; point cache_ttl to a question that is declared after it and you get invalid_when; set equals: "yes" on a boolean and you get invalid_when (“must be true or false”). Fix them back and run it again.
6

Create the template and deploy both variants

The first call creates only the application, with SQLite on its own disk. The second creates a stack with the app, a Postgres and a Redis: the deploy creates the databases, creates the app with the certificates already in place, fills the variables, starts it and waits for /healthz. Each deploy is a job with an idempotency key, and a failure undoes everything it created.Open the address of each app and reload: the counter goes up in both. Then run vertra stack list and vertra stack get <id> to see the members, the connections between them and the template the stack came from.
7

Live with the stack

The stack page lists how each variable of the app is connected to each database, and the How to connect panel shows code for the user’s language. When a database password or certificate is reset, the variables and the certificate files of the app are recalculated and the app needs a restart, which the panel and the API announce. When you publish a new version of the template, vertra stack update <id> --check shows what would change before you commit to it. See Stacks and Updates and rollback.

Troubleshooting

Next steps

Last modified on October 30, 2026