Skip to main content
This page captures the reasoning behind the main architectural decisions in sitectl. Understanding the “why” makes it easier to contribute in a way that stays consistent with the project’s direction.

Why not just use Docker Contexts?

While Docker’s native context feature handles basic Docker daemon connections, sitectl is purpose-built for projects and adds:

Remote operations

SFTP file operations and clearer SSH error handling beyond what Docker’s own context system exposes.

Container utilities

General helpers to resolve service names to containers, extract secrets and env vars for exec commands, and inspect container network details.

-first design

Automatically sets the equivalent of DOCKER_HOST, COMPOSE_PROJECT_NAME, COMPOSE_FILE, and COMPOSE_ENV_FILES from the active sitectl context.

Plugin model

Plugins extend sitectl without requiring changes to the core binary. The core binary discovers plugins by name convention (sitectl-<plugin> on $PATH) and delegates commands to them. This means:
  • Stack-specific logic stays in the plugin, not in core
  • Plugins can be installed and upgraded independently
  • Plugins can include other plugins — ISLE includes Drupal, so operators of ISLE sites get Drupal commands automatically
See Plugin hierarchy for how dispatch works at the code level. Runtime fan-out uses one private JSON RPC entrypoint, __sitectl-rpc, instead of one hidden command per capability. Keeping one transport command gives core sitectl one place to enforce protocol versioning, stdout/stderr behavior, structured errors, and SDK dispatch. Plugin authors still implement small typed handlers through the SDK. The RPC contract is versioned in lockstep between core and plugins. Request envelopes, response envelopes, params, and result payloads use explicit lower snake_case JSON tags, and typed request builders in pkg/plugin should be preferred over hand-written JSON maps.

Service component ownership

Reusable infrastructure services that are self-contained and cross-cutting belong in sitectl core, not in one plugin per service and not copied into every application plugin. Traefik, MariaDB, Solr, Valkey, and Memcached are core service command namespaces because many application stacks use them and the operational behavior is generic. Those services may still run as standalone Compose projects. Core owns the shared command surface and helpers, while application compose projects own target-specific wiring such as dependencies, environment variables, routes, and app-specific volume mounts. See Service components for the full architecture.

SDK runner interfaces

Where a core sitectl command needs plugin-specific behavior, the SDK defines a typed runner interface. The plugin implements the interface, registers it, and the SDK exposes it through the single private __sitectl-rpc entrypoint. This gives plugin authors a structured, type-safe extension point rather than requiring them to write raw transport code. Current runner interfaces:
InterfaceRegistered byRPC methodUser command
DebugRunnerRegisterDebugRunnerdebug.runsitectl debug
DeployRunnerRegisterDeployRunnerdeploy.runsitectl deploy
ConvergeRunnerRegisterConvergeRunnerconverge.runsitectl converge
SetRunnerRegisterSetRunnerset.runsitectl set
ValidateRunnerRegisterValidateRunnervalidate.runsitectl validate
sitectl validate is distinct from the others: core runs its own validators first, then invokes the plugin’s validate.run method, decodes the returned structured results, and merges everything before rendering. The plugin does not render; it just returns structured data. Runner diagnostics, progress, and prompts should write to stderr. Stdout is captured by the RPC layer for the response envelope and may not be displayed directly by the caller.

Component model

The component model was designed to solve two problems:
  1. Initial setup — making it easy to start a site with the right capabilities enabled, without manually editing files
  2. Incremental adoption — existing sites can adopt new upstream capabilities by turning a component on, rather than hand-editing files and hoping nothing breaks
The conservative default is that the upstream project template ships as-is. Components are an explicit override. This keeps the barrier to entry low while giving experienced operators a structured path to customization. The top-level sitectl set and sitectl converge commands are the operator-facing surface for the component model. sitectl component describe, sitectl component set, and sitectl component reconcile remain as lower-level commands backed by the same __sitectl-rpc protocol methods. See Component development for how to define a new component.

Why , not Kubernetes

Though isn’t designed for massive-scale orchestration, the applications hosted by most institutions rarely require more than modest scaling. The real advantage of is the developer experience. Because the exact same orchestration runs in both development and production — with only minor environmental tweaks — you can reliably mirror production on your local machine. This provides built-in deployment safety long before your CI pipeline runs a single test. We could have spent resources building Kubernetes operators for various LAC-GLAM stacks instead of creating sitectl. But sitectl was a deliberate choice: it empowers institutions to adopt open-source projects without the hurdle of hiring a Kubernetes administrator or absorbing the heavy operational overhead of a Kubernetes cluster. The goal was to let institutions adopt open-source software without being blocked by infrastructure complexity.