Core principle
If an individual service is cross-cutting, self-contained, and used by many applications, it belongs in the coresitectl command.
That means:
- MariaDB operations live under
sitectl mariadb ... - Traefik ingress operations live under
sitectl traefik ... - Solr operations live under
sitectl solr ... - Valkey operations live under
sitectl valkey ... - Memcached operations live under
sitectl memcached ...
Ownership boundaries
Coresitectl owns cross-cutting service commands and helpers:
- Context-aware Docker execution
- Backup, restore, and sync flows for shared databases
- Ingress TLS mode selection
- Bot mitigation controls
- Generic health, logs, and service inspection helpers
- Component merge mechanics and status detection
- Base Compose service definitions
- Default volumes, networks, secrets, configs, and health checks
- Makefile targets for direct standalone operation
- App service dependencies, such as
depends_on - Environment variables that point an app at MariaDB, Solr, Valkey, or Memcached
- App-specific Traefik route files, labels, and middleware wiring
- App-specific volumes, config files, and bootstrap defaults
- App CLIs such as Drush, WP-CLI, OJS tools, or ArchivesSpace API helpers
- App-specific sync, migration, cache, search, and diagnostic behavior
- Create flows and prompts that are unique to that application
Command shape
Keep service command namespaces stable even when the implementation moves into core. Examples:Traefik ingress
All application stacks use Traefik for ingress. Traefik is therefore a first-class core service area, not an app-specific add-on. Core Traefik support must cover:httpfor plain local or internal HTTPmkcertfor local trusted development certificatesletsencryptfor ACME-managed production certificatesself-managedfor bring-your-own certificate/key material- Bot mitigation controls that app route files can attach to
sitectl. An app plugin registers it with the router and route-file details for that app, then delegates mutation to the core helper:
protectRoutes, without copying the plugin installation, Traefik command, Turnstile environment, or middleware rendering code.
One compose contract
A reusable service should not maintain separate “standalone” and “embedded” Compose files when the real difference is target wiring. For example, Solr standalone and Solr inside Drupal are the same base service. The Drupal Compose project can add target-specific changes, such as a Drupal Solr config volume or adepends_on relationship. The service contract should stay inspectable and deployable without hiding large YAML fragments inside Go string constants.
Use one canonical service definition when possible and apply target rules when the service is merged into a larger project.
Merge model
When a service component is enabled, sitectl:- Reads the canonical service definition.
- Adds its services, networks, volumes, secrets, and configs to the target Compose project.
- Applies target integration rules from the application project or registered component.
- Writes the updated Compose file.
- Removes the service from the target Compose project.
- Removes dependencies and target integration rules that only make sense while the service exists.
- Prunes unused Compose resources when they are no longer referenced.
- Leaves data migration decisions explicit. Removing a local service does not imply that data has been safely migrated elsewhere.
Dispositions
Service components normally support:| Disposition | Meaning |
|---|---|
enabled | Run this service in the current Compose project |
disabled | Do not run this service in the current Compose project |
distributed | The service exists outside this Compose project and the application should be wired to that external service |
distributed is important. These services are useful as standalone projects, so application stacks should not assume every dependency must be colocated. A Drupal stack might use a local Solr today and an external Solr tomorrow. The component model should make that transition explicit.
Target rules
Application Compose projects should define only the integration rules they own. Good target rules:- Restore
services.drupal.depends_on.solr - Add a Drupal Solr config volume to the Solr service
- Set app-specific environment variables that point to a distributed service
- Attach app routes to Traefik middleware supplied by core ingress helpers
- Override ArchivesSpace Solr image, command, and volume target
- Recreate the entire imported service in the application plugin
- Keep another copy of the service Compose YAML in the application plugin
- Hide large YAML fragments inside Go string constants
- Add a dedicated CLI plugin for a small single-service dependency just to expose generic operations
When to add a core service namespace
Add the service to core when most of these are true:- It is a single self-contained service.
- Multiple application stacks use it.
- Its operations are generic across apps.
- It can reasonably run as a standalone Compose project.
- The command namespace would be service-oriented, such as
sitectl mariadb ..., not application-oriented.
Application plugin composition
Application plugins should include other application plugins only when there is a real application hierarchy. ISLE includes Drupal because every ISLE site is also a Drupal site. Application plugins should not include service plugins for MariaDB, Solr, Valkey, Memcached, or Traefik. A context withplugin: wp, plugin: drupal, or another app plugin can still run core service commands such as sitectl mariadb backup because those commands are part of core sitectl.
Use these defaults unless the template changes:
| App plugin | Include set |
|---|---|
| ArchivesSpace | none |
| Drupal | none |
| ISLE | drupal |
| OJS | none |
| Omeka Classic | none |
| Omeka S | none |
| WordPress | none |
Release order
Because shared service behavior lives in core, changes should be released in this order:- Release the sitectl core changes that add or change shared service commands, helpers, or component machinery.
- Update standalone service Compose projects when their templates change.
- Update application compose templates when their target wiring changes.
- Release application plugin changes for app-specific workflows.
go work. Do not add local replace ... => ../... directives to app plugin go.mod files.
