Application Templates

Available from replication-manager 3.1.43.

An application is deployed next to the database cluster from a template: a TOML file of the template repository (prov-app-template-repo, the public cloud18-templates) or of the cluster's local templates. Add it from the GUI, from the API (POST /api/clusters/{cluster}/actions/addserver/{name}/{port}/app/{template}) or from the MCP tool app-add, then provision it.

10.6.1 Substitution keys

A template is substituted with the cluster's data before it is loaded. The raw file must be valid TOML before the substitution, so selectors take bare values.

key value
{{app.name}}, {{app.port}}, {{app.host}} the app being added; host is its service name in the cluster namespace (<app>.<cluster>.svc.<orchestrator cluster>)
{{name}} the cluster name
{{config.<field>}} a cluster setting, e.g. {{config.provAppAgents}}, {{config.cloud18Domain}}
{{proxies.0.name}}, {{proxies.0.writePort}}, {{proxies.#.name}} the proxies: the first one, or all names comma-joined
{{apps.#(name==valkey1).host}} another app of the cluster, by name: host, port, config.deployment.variables.#(name==X).value, db.*

The namespace glues the apps together: every app answers on its service name, so a dependent app references another one through its environment variables, e.g. REDIS_CACHE = "redis://{{apps.#(name==erp-cache).host}}:6379". The referenced app must exist in the cluster when the dependent one is added (not necessarily provisioned).

A template carries plain default values, never free placeholders: an unresolved key refuses the add with the list of missing keys.

10.6.2 A database for the app (app.db)

The cluster is the database service, so an app asks it for its own schema:

app-db-auto-create = true

[[deployment.variables]]
  name = "DB_HOST"
  type = "env"
  value = "{{proxies.0.name}}"
[[deployment.variables]]
  name = "DB_NAME"
  type = "env"
  value = "{{app.db.schema}}"
[[deployment.variables]]
  name = "DB_USER"
  type = "env"
  value = "{{app.db.user}}"
[[deployment.variables]]
  name = "DB_PASSWORD"
  type = "secret"
  value = "{{app.db.password}}"

Referencing {{app.db.*}} is enough to request the database. At add time the app settings app-db-schema and app-db-user default to the app name (lower-case, a-z0-9_), and app-db-pass to a generated password stored encrypted; all three can be set by hand in the app Overview or through the app settings route. At provision, replication-manager creates the schema, the user for % (the configurator disables name resolution, so a hostname-bound account could never log in; the database is reachable from the cluster network only) and grants it all privileges on that schema only. Every statement is in the SQL log.

An existing schema or user is never touched. The app records what it created (app-db-owned); a provision that finds the schema or the user already present without that mark is refused, the app shows the state APPERR008 and nothing is altered. Free the names with app-db-schema / app-db-user, or set app-db-owned when the objects really belong to the app. On an owned account the provision re-applies the stored password, and setting app-db-pass rotates it at once. Dropping the app never drops the schema or the user.

Dependent processes share the owner's database: {{apps.#(name==erp-backend).db.user}}, .db.schema, .db.password (as a secret variable). A prov-app-docker-cmd reads the password from its environment variable, never from the key.

Settings: app-db-auto-create (bool), app-db-schema, app-db-user, app-db-pass, app-db-owned (bool), on the route /api/clusters/{cluster}/apps/{app}/settings/actions/set/{setting}/{value} (or switch for the booleans). The MCP tools app-add and list-cluster-apps answer the db object.

10.6.3 Files on the S3 provider (s3-mounts)

An app that must share files between its processes mounts a bucket of the cluster's S3 provider app (template rustfs, app-s3-provider = true, its volume is metered as archive, BAU) instead of a shared block volume:

[[deployment.paths]]
  dockerpath = "/home/frappe/frappe-bench/sites/erpnext/public/files"
  name = "path-s3-erp-public"
  srcname = "erp-public"
  srcpath = "mnt/erp-public/"
  srctype = "s3"
  volumename = "erp-mounts"

[deployment.storages]
  [[deployment.storages.s3-mounts]]
    name = "erp-public"
    bucket = "erp-public"
    endpoint = "{{apps.#(name==rustfs1).host}}:9000"
    accesskey = "{{apps.#(name==rustfs1).config.deployment.variables.#(name==RUSTFS_ACCESS_KEY).value}}"
    secretkey = "{{apps.#(name==rustfs1).config.deployment.variables.#(name==RUSTFS_SECRET_KEY).value}}"
    volumename = "erp-mounts"
    volumedir = "mnt/erp-public"
    uid = "1000"
    gid = "1000"

A mount sidecar presents the bucket inside the container, as the owner uid/gid (default 33, www-data). The provider's credentials are read from its variables, MinIO (MINIO_ROOT_USER/MINIO_ROOT_PASSWORD), RustFS (RUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY) or AWS names.

10.6.4 Start timeout

prov-app-start-timeout (default 2m) is the container start and image pull timeout written in the service definition, per app or for the cluster.

10.6.5 Example: ERPNext as linked apps

The erpnext templates deploy ERPNext as one container per process, no shared volume: two valkey apps (erp-cache, erp-queue), erp-backend (owns the database through app.db and the site, creates it on the empty schema at first start), erp-worker, erp-scheduler, erp-websocket, erp-frontend (the https route). The site files live on the S3 provider (rustfs1), the database is the cluster's MariaDB through the proxy. Add them in that order with those names.