Skip to main content

The manifest

A manifest has two halves: metadata says what the app is, spec says how it runs. This page walks through both in the order they usually appear. The full schema is schema/manifest-v2.json in the spec repo.

The frame​

apiVersion: obacht.dev/v2
kind: Template
metadata:
# what the app is
spec:
# how it runs

metadata​

metadata:
name: uptime-kuma # the template id: kebab-case, unique, never changes
displayName: Uptime Kuma # what the store shows
version: "1.0.3" # semver of the template, not of the app
description: |
Self-hosted status and uptime monitoring.
author: your-name
license: MIT
homepage: https://github.com/louislam/uptime-kuma
tags: [monitoring, status-page]
trustLevel: community

name, displayName and version are required. version is the version of your template. Every change you publish bumps it: patch for a new image digest, minor for a new setting, major when existing installs have to change their settings.

Set trustLevel: community. official is reserved for templates Obacht maintains.

spec.minSpecVersion​

spec:
minSpecVersion: "v2.2"

Required. The lowest spec revision your template needs. Devices with an older agent refuse it. Revisions after v2.1 only add things, so raise this only when you use a feature the agent itself has to understand. Use v2.2 if you mark settings as immutable, otherwise v2.1 is enough. Purely visual features (the timezone, email and domain field types, advanced, hosting hints) need no raise.

spec.compatibility​

  compatibility:
architectures: [linux/arm64, linux/amd64]
resources:
minRamMb: 128
minDiskMb: 256

Required, and architectures is required inside it. List what your images actually ship. The store only offers your template on devices it can run on, and greys it out with "Needs 2 GB RAM, this device has 1 GB" where memory or disk are too small.

Be honest about resources. A Raspberry Pi 4 with 2 GB has about 1.4 GB free after the system. Count the download, the first start and the data your app builds up.

Optional: devices (an allow list like [raspberry-pi-4, raspberry-pi-5, generic-x86_64, mac]) and excludeDevices (the opposite).

spec.runtime​

Where your app is described. Two runtimes are open to community templates:

RuntimeUse it forExample
containerOne image that brings everything it needs, often with SQLite inside.Uptime Kuma
composeSeveral containers that belong together, typically an app and its database.Ghost with MySQL

Prefer container when the app can run alone. One container is easier to update, smaller on disk and quicker to review.

Each install of a template gets its own instance ID. Use ${instance.id} wherever two installs on the same device must not collide, for example in a data path: /var/lib/obacht/uptime-kuma/${instance.id}/data.

spec.services​

  services:
- name: web
targetType: container_port
targetPort: 3001

The port the web address points at. Caddy on the device terminates HTTPS and forwards plain HTTP to this port inside the container. Never publish ports to the host yourself. For a Compose bundle add targetService, the name of the service that answers.

If your app serves its start page under a path, add appPath: /admin/ and a visit to the bare address is redirected there.

readiness​

"Installed" only means the container runs. Many apps need longer before they answer: database migrations, a first index, a model download. So after install the device keeps asking your service over HTTP until it answers, and the install sheet holds the Ready step until then. While it waits, anyone opening the address gets a small "still starting" page instead of an error.

That happens for every template, with defaults: a GET /, accepted on any status from 200 to 399 and on 401 or 403 (a login wall still means the app is up), for up to 300 seconds. Tune it on the service when the defaults do not fit:

  services:
- name: web
targetType: container_port
targetPort: 8080
readiness:
path: /health # default "/"
expectStatus: [200] # default: 200 to 399, plus 401 and 403
timeoutSeconds: 600 # default 300, allowed 10 to 1800
  • Set path and expectStatus when the start page answers long before the app works, or when the app has a health endpoint.
  • Raise timeoutSeconds for a slow first start. After the timeout the app counts as unhealthy, it stays installed and the sheet tells the user it is still starting.

readiness is new in v2.9. Older agents ignore it and report the plain install state, so you do not need to raise minSpecVersion for it.

spec.configSchema​

The settings the user fills in during install. Each entry becomes one field in the install sheet:

  configSchema:
- key: timezone
label: Timezone
type: timezone
default: "UTC"
description: For schedules and the clock in the app.

Reference a value anywhere in the runtime as ${cfg.timezone}. What each type looks like and which flags exist is on Settings and how they look.

spec.secretsSchema​

Passwords the user never has to see, typically for the database inside a bundle:

  secretsSchema:
- key: db_password
length: 32
charset: alphanumeric
secrets:
- MYSQL_PASSWORD

The device generates each value once, on first install, stores it locally and never sends it anywhere. Obacht's servers never see it. Use it as ${secret.db_password}. List the environment variable names that carry secrets under secrets, so they are kept out of logs.

Charsets: alphanumeric, alphanumeric_symbols, hex, base64, base64_bytes. Pick alphanumeric unless the app needs something else. Symbols tend to break connection strings.

spec.gettingStarted​

  gettingStarted: |
1. Open the app and create the first account. The first account is the admin.
2. Mail is not set up, so password resets do not work yet.

Shown on the Ready step after install ("First steps") and on the app page. Markdown, without images or HTML. Write what someone has to do in the first five minutes, especially anything time-critical like "the first visitor becomes admin". Placeholders like <your-domain> are shown as written, so phrase them so they read naturally.

spec.hosting​

  hosting:
recommendBasicAuth: true
basicAuthReason: "The API has no login of its own and is easy to overload."

For apps without their own login. The install sheet then switches Password protection on by default and shows your reason. Leave it out for apps with an account system.

Placeholders​

PlaceholderBecomes
${instance.id}The ID of this install.
${cfg.<key>}The value of a setting.
${secret.<key>}A generated secret.

They work in commands, environment values, volume paths, labels and the Compose body.

Next: Settings and how they look.