Directory Structure
Every template is a directory. The directory name is the app's unique identifier (e.g., paperless-ngx, jellyfin). Here's what a complete template looks like:
my-app/
├── manifest.yaml # App metadata (name, description, category, ...)
├── icon.svg # App icon shown in the catalog
├── lawn-compose/ # Versioned compose files
│ ├── v1.0.0.yaml # ↳ first version
│ └── v1.2.0.yaml # ↳ newer version (Lawn picks the latest)
└── init-files/ # Files copied into the container (optional)
└── nginx.conf # ↳ referenced from x-lawn.initFiles
manifest.yaml — App metadata
The manifest tells Lawn everything it needs to display the app in the catalog. Users see this when they browse and search for apps.
id: my-app
name: My App
tagline: A short one-liner about what this app does.
description: |
A longer **markdown** description shown on the app detail page.
icon: server.rack # SF Symbol fallback (used when no icon file exists)
category: Utilities
webPort: 8080
All fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier — must match the directory name |
name | string | Yes | Display name |
tagline | string | No | Short one-line description |
description | string | Yes | Full markdown description |
icon | string | Yes | SF Symbol name used as fallback when no icon file exists |
category | string | Yes | One of: Documents, Media, Development, Networking, Utilities, Databases, Monitoring |
tags | string | No | Search tags |
features | string | No | Feature bullet points |
screenshots | string | No | Screenshot identifiers |
links | Link | No | External links (github, docs, website) |
webPort | int | No | The container port for the web UI |
webScheme | string | No | URL scheme: http (default) or https |
webPath | string | No | Path appended to the web URL (e.g., /admin/) |
icon.svg — App icon
The app icon shown in the catalog and instance detail views. The file must be named icon with one of the supported extensions:
| Format | Extension | Notes |
|---|---|---|
| SVG | .svg | Preferred — scales to any size |
| PNG | .png | Use at least 256×256 pixels |
| HEIF | .heif | macOS native format |
.pdf | Vector format |
If no icon file is present, Lawn falls back to the SF Symbol from the manifest's icon field.
lawn-compose/ — Container configuration
This directory holds versioned compose files. Each file defines how the app runs — container images, environment variables, volumes, ports, health checks, and Lawn extensions.
Versioning — the filename determines the version. Lawn uses semantic versioning (semver) , where version numbers follow the pattern major.minor.patch. The major number changes for big updates, minor for new features, and patch for bug fixes.
| Format | Example | Use case |
|---|---|---|
v32.yaml | Nextcloud 32 | Major-only |
v10.10.yaml | Jellyfin 10.10 | Major.minor |
v2.20.7.yaml | Paperless-ngx 2.20.7 | Full semver |
Lawn picks the latest version by default. When you release a new version of the app, add a new compose file — don't overwrite the old one. Existing instances track which version they were installed with.
See Format for compose file structure and Extensions for x-lawn features.
init-files/ — Startup files (optional)
Files in this directory are copied into the container's volume mounts before each start. This is useful for configuration files that need to exist on disk when the app boots — for example, a custom nginx config or a default settings file.
Each file is referenced by name in the compose file's x-lawn.initFiles array, which maps it to a path inside the container. Files are overwritten on every start, so the template always provides the latest version.