Templates

Complete Example

A full template showing all features working together.
The catalog is community-driven. Want to add an app? Open a PR on GitHub. Support for custom catalogs from other sources is coming soon.

This page walks through a complete template that uses every major feature: multiple services, generated secrets, user-provided variables, system variables, health checks, notices, environment metadata, config options, init files, resource limits, and localization.

Directory Structure

apps/
└── example-app/
    ├── manifest.yaml
    ├── icon.svg
    ├── locales/
    │   └── en.json
    ├── lawn-compose/
    │   └── v1.0.0.yaml
    └── init-files/
        └── custom.config.php

manifest.yaml

The manifest defines how the app appears in the catalog.

manifest.yaml
id: example-app
name: Example App
tagline: A full-featured example template
description: |
  An example app that demonstrates all Lawn template features.

  **Highlights:**
  Multi-service architecture with a PostgreSQL database,
  automatic credential generation, and health monitoring.
icon: app.badge.checkmark
category: Utilities
compatibility:
  minLawnVersion: 0.2.0
tags:
  - example
  - demo
features:
  - Automatic admin password generation
  - PostgreSQL database with health checks
  - Configuration file injection
links:
  - url: https://github.com/example/example-app
    type: github
  - url: https://example.com/docs
    type: documentation
webPort: 8080

lawn-compose/v1.0.0.yaml

The compose file defines the services, variables, and Lawn extensions. All user-facing strings (display names, descriptions, labels) live in the localization file — the YAML contains only structural configuration.

lawn-compose/v1.0.0.yaml
x-lawn:
  compatibility:
    minLawnVersion: 0.2.0
  config:
    POSTGRES_PASSWORD:
      source: generatedSecret
      length: 32
    SECRET_KEY:
      source: generatedSecret
      length: 50
    ADMIN_PASSWORD:
      source: userProvided
    LOG_LEVEL:
      value: info
      weight: 10
      options:
        debug:
          compose:
            services:
              app:
                environment:
                  LOG_LEVEL: debug
        info:
          compose:
            services:
              app:
                environment:
                  LOG_LEVEL: info
        warning:
          compose:
            services:
              app:
                environment:
                  LOG_LEVEL: warning
    ENABLE_SIGNUP:
      inputType: boolean
      value: true
      weight: 20
      options:
        "true":
          compose:
            services:
              app:
                environment:
                  ENABLE_SIGNUP: "true"
        "false":
          compose:
            services:
              app:
                environment:
                  ENABLE_SIGNUP: "false"

services:
  app:
    image: example/app:1.0.0
    depends_on:
      - db
    environment:
      DATABASE_URL: "postgres://postgres:${POSTGRES_PASSWORD:-changeme}@db:5432/app"
      SECRET_KEY: "${SECRET_KEY:-changeme}"
      ADMIN_USER: admin
      ADMIN_PASSWORD: "${ADMIN_PASSWORD:-admin}"
      TZ: "${TIMEZONE:-UTC}"
      PUID: "${UID:-1000}"
      PGID: "${GID:-1000}"
    volumes:
      - "./data:/app/data"
      - "./config:/app/config"
    ports:
      - "8080:8080"
    deploy:
      resources:
        limits:
          memory: 1024M
    x-lawn:
      healthChecks:
        - id: web-ui
          type: http
          path: /health
          expectedStatus: 200
      notices:
        - id: login-credentials
          style: info
          elements:
            - id: username
              type: field
              value: admin
            - id: password
              type: sourceField
              source: ADMIN_PASSWORD
              container: app
            - id: change-password
              type: text
      environment:
        ADMIN_USER:
        TZ:
        PUID:
        PGID:
      initFiles:
        - source: "custom.config.php"
          containerPath: "/app/config/custom.config.php"
      createDirectories:
        - "./data/uploads"
        - "./data/cache"

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-changeme}"
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - "./postgres:/var/lib/postgresql/data"
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    x-lawn:
      healthChecks:
        - id: db-ready
          type: command
          command: ["/bin/sh", "-c", "pg_isready -U postgres"]
      environment:
        POSTGRES_DB:
        POSTGRES_USER:
        PGDATA:

locales/en.json

The localization file provides all user-facing strings. Keys match the variable names, health check IDs, notice IDs, and element IDs from the YAML.

locales/en.json
{
  "config": {
    "POSTGRES_PASSWORD": {
      "displayName": "Database Password",
      "description": "PostgreSQL superuser password. Auto-generated during install."
    },
    "SECRET_KEY": {
      "displayName": "Secret Key",
      "description": "Application secret key for session signing."
    },
    "ADMIN_PASSWORD": {
      "displayName": "Admin Password",
      "description": "Password for the admin account."
    },
    "LOG_LEVEL": {
      "displayName": "Log Level",
      "description": "Controls how much detail the application logs.",
      "options": {
        "debug": { "label": "Debug" },
        "info": { "label": "Info" },
        "warning": { "label": "Warning" }
      }
    },
    "ENABLE_SIGNUP": {
      "displayName": "Public Sign-up",
      "description": "Allow new users to create accounts.",
      "options": {
        "true": { "label": "On" },
        "false": { "label": "Off" }
      }
    }
  },
  "services": {
    "app": {
      "environment": {
        "ADMIN_USER": {
          "description": "Username for the admin account."
        },
        "TZ": {
          "description": "Timezone for date display and scheduling."
        },
        "PUID": {
          "description": "User ID for file ownership inside the container.",
          "group": "Permissions"
        },
        "PGID": {
          "description": "Group ID for file ownership inside the container.",
          "group": "Permissions"
        }
      },
      "healthChecks": {
        "web-ui": {
          "description": "Web UI is accessible and responding."
        }
      },
      "notices": {
        "login-credentials": {
          "title": "Login Credentials",
          "elements": {
            "username": { "label": "Username" },
            "password": { "label": "Password" },
            "change-password": { "content": "Change these in **Settings > Security** after your first login." }
          }
        }
      }
    },
    "db": {
      "environment": {
        "POSTGRES_DB": {
          "description": "Name of the PostgreSQL database."
        },
        "POSTGRES_USER": {
          "description": "PostgreSQL username."
        },
        "PGDATA": {
          "description": "Data directory inside the container. Do not change."
        }
      },
      "healthChecks": {
        "db-ready": {
          "description": "Database is accepting connections."
        }
      }
    }
  }
}

init-files/custom.config.php

Init files are copied into the container's volume mounts before each start. The containerPath must fall within a declared volume mount.

init-files/custom.config.php
<?php
// This file is injected by Lawn on every container start.
// It reads configuration from environment variables set by the compose file.

$config = [
    'database' => getenv('DATABASE_URL'),
    'timezone' => getenv('TZ') ?: 'UTC',
];

What Each Feature Does

FeatureWherePurpose
x-lawn.compatibilityTop-levelMinimum Lawn version required for this template
x-lawn.configTop-levelDeclares secrets, user-provided values, and config options
generatedSecretConfigRandom password created at install time
userProvidedConfigValue the user supplies during install
Config optionsConfigSelectable choices with compose overlays
inputType: booleanConfigToggle switch for on/off settings
weightConfigDisplay ordering in the settings UI
locales/en.jsonLocalizationDisplay names, descriptions, option labels, notice strings
System variablesEnvironmentTIMEZONE, UID, GID — always available
healthChecksPer-serviceHTTP and command checks for readiness
noticesPer-serviceDismissible banners with credentials and guidance
environmentPer-serviceFormat hints for env vars
initFilesPer-serviceConfig files injected from template into volumes
createDirectoriesPer-serviceHost directories created before container start
deploy.resources.limitsPer-serviceMemory and CPU constraints

Testing

Validate and test the template with lawn template:

# Validate manifest, compose, and localization
lawn template validate .

# Full end-to-end test (pull, start, health check)
lawn template test .

Since ADMIN_PASSWORD is userProvided, the full test requires the value to be supplied:

lawn template test . --var ADMIN_PASSWORD=testpassword123