Complete Example
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.
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.
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.
{
"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.
<?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
| Feature | Where | Purpose |
|---|---|---|
x-lawn.compatibility | Top-level | Minimum Lawn version required for this template |
x-lawn.config | Top-level | Declares secrets, user-provided values, and config options |
generatedSecret | Config | Random password created at install time |
userProvided | Config | Value the user supplies during install |
| Config options | Config | Selectable choices with compose overlays |
inputType: boolean | Config | Toggle switch for on/off settings |
weight | Config | Display ordering in the settings UI |
locales/en.json | Localization | Display names, descriptions, option labels, notice strings |
| System variables | Environment | TIMEZONE, UID, GID — always available |
healthChecks | Per-service | HTTP and command checks for readiness |
notices | Per-service | Dismissible banners with credentials and guidance |
environment | Per-service | Format hints for env vars |
initFiles | Per-service | Config files injected from template into volumes |
createDirectories | Per-service | Host directories created before container start |
deploy.resources.limits | Per-service | Memory 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