|
| 1 | +--- |
| 2 | +title: Application plugin template |
| 3 | +description: How to build a new Compose-backed sitectl application plugin from sitectl-app-tmpl. |
| 4 | +--- |
| 5 | + |
| 6 | +import { Compose } from "/snippets/compose-tooltip.mdx"; |
| 7 | + |
| 8 | +Use [`sitectl-app-tmpl`](https://github.com/libops/sitectl-app-tmpl) when adding a new application plugin for a standalone <Compose /> template repository. |
| 9 | + |
| 10 | +The template is intentionally thin. The application repository owns the Compose workload. Core `sitectl` owns shared lifecycle commands. The plugin contributes app-specific metadata and helpers. |
| 11 | + |
| 12 | +## Command Ownership |
| 13 | + |
| 14 | +| Concern | Owner | Examples | |
| 15 | +|---|---|---| |
| 16 | +| Compose lifecycle | Core `sitectl` | `sitectl compose up`, `sitectl compose down`, `sitectl compose logs`, `sitectl compose ps` | |
| 17 | +| Create flow metadata | App plugin | `CreateSpec`, template repo, init artifacts, image specs | |
| 18 | +| App-specific commands | App plugin | `sitectl <app> exec ...`, API wrappers, application maintenance helpers | |
| 19 | +| Shared service operations | Core `sitectl` | `sitectl mariadb backup`, `sitectl solr info`, `sitectl traefik tls` | |
| 20 | +| Cross-cutting reports | Core command plus plugin runner | `sitectl debug`, `sitectl validate`, `sitectl healthcheck` | |
| 21 | + |
| 22 | +New app plugins should usually call `RegisterComposeTemplateCreateRunner`, not `RegisterStandardComposeTemplate`. The latter still exists for plugins that deliberately want direct namespace lifecycle commands, but first-class app plugins should let core `sitectl compose` provide the shared lifecycle surface. |
| 23 | + |
| 24 | +## Create Definition Contract |
| 25 | + |
| 26 | +The create definition is more than a clone recipe. It is also the desired state metadata used by core `sitectl compose up` to decide whether a local checkout needs first-run init or image build work. See [Compose reconcile contract](/contributing/compose-reconcile) for the shared core behavior. |
| 27 | + |
| 28 | +```go |
| 29 | +func createDefinition() plugin.CreateSpec { |
| 30 | + return plugin.CreateSpec{ |
| 31 | + Name: "default", |
| 32 | + Default: true, |
| 33 | + DockerComposeRepo: TemplateRepo, |
| 34 | + DockerComposeBranch: TemplateBranch, |
| 35 | + DockerComposeInit: []string{ |
| 36 | + "if [ ! -f .env ]; then cp sample.env .env; fi", |
| 37 | + "docker compose run --rm init", |
| 38 | + }, |
| 39 | + InitArtifacts: []plugin.InitArtifact{ |
| 40 | + {Path: ".env"}, |
| 41 | + {Path: "secrets/DB_ROOT_PASSWORD"}, |
| 42 | + {Path: "secrets/APP_DB_PASSWORD"}, |
| 43 | + }, |
| 44 | + InitVolumes: []plugin.InitVolume{ |
| 45 | + {Name: "mariadb-data"}, |
| 46 | + }, |
| 47 | + DockerComposeBuild: []string{ |
| 48 | + "docker compose pull --ignore-buildable", |
| 49 | + "docker compose build --pull", |
| 50 | + }, |
| 51 | + Images: []plugin.ComposeImageSpec{ |
| 52 | + { |
| 53 | + Service: "app", |
| 54 | + Image: "libops/app:local", |
| 55 | + BuildPolicy: plugin.BuildPolicyIfNotPresent, |
| 56 | + }, |
| 57 | + }, |
| 58 | + DockerComposeUp: []string{ |
| 59 | + "docker compose up --remove-orphans -d", |
| 60 | + }, |
| 61 | + DockerComposeDown: []string{"docker compose down"}, |
| 62 | + DockerComposeRollout: []string{"./scripts/rollout.sh"}, |
| 63 | + } |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +`DockerComposeInit` should be idempotent. It may create `.env`, run an `init` service, and write deterministic secret files. It must not destroy existing application data when run again. |
| 68 | + |
| 69 | +`InitArtifacts` should list files that prove init has completed. Prefer explicit files over probing container state. Use `ValueFrom: plugin.InitArtifactValueFromHostUID` for a UID marker file when generated files need to match the local host user. |
| 70 | + |
| 71 | +`InitVolumes` should list named Compose volumes that prove first-start runtime state exists, such as database volumes and application file/upload volumes. Core resolves those names through `docker compose config`, so custom Compose project names and explicit volume names still work. |
| 72 | + |
| 73 | +`Images` should list local images the template expects to build. Use `BuildPolicyIfNotPresent` for normal local images, `BuildPolicyAlways` only when every `compose up` should rebuild, and `BuildPolicyNever` for services that should never trigger the build phase. |
| 74 | + |
| 75 | +## Image Overrides |
| 76 | + |
| 77 | +Core `sitectl image set` writes `docker-compose.override.yml` for local contexts: |
| 78 | + |
| 79 | +```bash |
| 80 | +sitectl image set --tag mariadb=11.4 |
| 81 | +sitectl image set --image app=ghcr.io/example/app:pr-123 |
| 82 | +sitectl image set --build-arg app.BASE_IMAGE=libops/app:php84 |
| 83 | +``` |
| 84 | + |
| 85 | +Image overrides affect reconcile: |
| 86 | + |
| 87 | +- an explicit image override for a service means core does not require the plugin's default image to exist locally |
| 88 | +- a build-arg override triggers the build phase so the new arguments are applied |
| 89 | +- the override file fingerprint is part of the reconcile cache key |
| 90 | + |
| 91 | +Use `--image` or `--build-arg` for app-specific services that are not known to the shared `--tag` map. |
| 92 | + |
| 93 | +## Template Checklist |
| 94 | + |
| 95 | +After creating a repository from `sitectl-app-tmpl`: |
| 96 | + |
| 97 | +1. Rename the Go module, binary, plugin name, display name, and release workflow outputs. |
| 98 | +2. Set `TemplateRepo`, `TemplateBranch`, `DefaultPath`, `AppService`, `AppImage`, `DatabaseService`, `DatabaseName`, and codebase rootfs constants. |
| 99 | +3. Update `CreateSpec` lifecycle commands, `InitArtifacts`, `InitVolumes`, and `Images` to match the real template repository. |
| 100 | +4. Tune project discovery in `SetComposeProjectDiscovery`. Use service names and durable codebase markers that identify the real project. |
| 101 | +5. Keep app-specific commands in the plugin namespace. Avoid registering `build`, `init`, `up`, `down`, `status`, `logs`, or `rollout` unless the app has a deliberate compatibility need. |
| 102 | +6. Use core service commands for shared operations. For example, document `sitectl mariadb backup app` rather than adding a thin app plugin alias. |
| 103 | +7. Implement debug, validate, and healthcheck runners through the SDK. Results should be structured; progress and diagnostics should go to stderr. |
| 104 | +8. Keep local workspace wiring in `go.work`; do not add sibling-module `replace` directives to `go.mod`. |
| 105 | +9. Update the integration script to create with `--setup-only`, start with `sitectl compose up`, and then run `sitectl healthcheck`. |
| 106 | +10. Add a plugin page to `sitectl-docs` for app-specific operations. |
| 107 | + |
| 108 | +## Local Verification |
| 109 | + |
| 110 | +Run the standard local workflow: |
| 111 | + |
| 112 | +```bash |
| 113 | +make work |
| 114 | +make test |
| 115 | +make install |
| 116 | +``` |
| 117 | + |
| 118 | +Then verify the installed plugin can be discovered and used through core commands: |
| 119 | + |
| 120 | +```bash |
| 121 | +sitectl create app-tmpl/default --path ./app --type local --checkout-source template --setup-only |
| 122 | +sitectl compose up |
| 123 | +sitectl healthcheck |
| 124 | +sitectl app-tmpl exec -- printenv |
| 125 | +``` |
0 commit comments