Chapters
On this page
DOC-04 / Technical reference · Chapter 09
Deployment & infrastructure
Describes the Synedre OS release pipeline and its tenants, including the asymmetry between ./deploy (preprod/AI) and ./ship (production, gated by fleet), the YAML dispatcher, the build-host-without-build-VPS pattern, and the doctrines associated with secrets and commits.
Deployment & infrastructure
This section describes the platform's deployment pipeline and its tenants: the asymmetry between the two deployment entrypoints, the configuration-driven dispatcher, the build-local-then-transfer pattern (no build on the target VPS), the special case of the mothership's self-deploy, and the associated doctrines (secrets, commit before deploy, schema drift management).
1. Asymmetry between ./ship and ./deploy
Two entrypoints coexist at the repository root. They do not target the same destination and do not share the same owner.
./deploy |
./ship |
|
|---|---|---|
| Target | Pre-production (or mothership live runtime / PROD for founder sites without a pre-prod) | Production |
| Owner | Agent / worker / cascade — systematic, without prompting | Fleet-gated: the agent/tick may ship if the tenant allows it; otherwise manual intervention is required |
| Git | Auto-commits dirty state; stays on the preprod branch |
checkout main → pull → merge preprod -X theirs → push origin main, then guaranteed return to preprod (EXIT trap) |
| Dirty guard | Silent auto-commit (except when on main: hard refusal) |
Blocking: refuses if the working tree is dirty, unless the --allow-dirty flag is set |
| Drift check | Single hardwired pre-prod target; any other tenant → explicit skip. Bypass via --skip-drift. |
Hub schema check (blocking) and tenant prod database check if the target is known. No equivalent --skip-drift: hub drift is strictly blocking. |
| Drift auto-apply | The drift engine generates and applies missing idempotent DDL statements (CREATE TABLE IF NOT EXISTS, ALTER TABLE ADD COLUMN IF NOT EXISTS) in a single transaction before any build or reload. Enabled by default; can be disabled on a per-run basis via environment variable. |
Drift is applied manually before the ship. |
| Post-deploy smoke | HTTP check of tenants, followed by a non-blocking visual smoke (disableable via environment variable). | Process environment check (anti-false-positive) then HTTP + content/JSON checks. |
| DB migrations | — | Lists pending .sql files in the relevant scope — non-blocking, not auto-applied (see §1ter). |
| Closing step | — | Offers to close worksites in test status (skip via --no-close). |
Doctrine:
./ship <tenant>targets production, fleet-gated: if the tenant allows it, the agent or tick may ship; otherwise manual intervention is required. An unattended nightly ship additionally requires a passing QA proof../deploy <tenant>is always triggered by the agent, systematically and without prompting — including on the mothership itself, where./deployrebuilds the live runtime (no separate pre-prod).
┌─────────────┐ ./deploy <tenant> ┌──────────────┐
AI → │ preprod │ ─────────────────────→ │ pre-prod / │
│ branch │ (auto, N times) │ live runtime │
└─────────────┘ └──────────────┘
│
│ Pre-prod review, validation
▼
┌─────────────┐ ./ship <tenant> ┌──────────────┐
ship → │ merge preprod│ ─────────────────────→ │ PRODUCTION │
│ → main │ (fleet-gated) │ │
└─────────────┘ └──────────────┘
Exception — public site without pre-prod: ./deploy is refused for the founder's personal site — this site runs directly in production on the CodeMyShop VPS. It is modified exclusively via ./ship.
Exception — founder tenants without pre-prod (synedre.com, codemyshop.com, and a third founder site): ./deploy points directly to production. For these tenants, the deploy = pre-prod boundary does not hold — the agent triggers ./deploy, but it modifies production. This is intentional; no dedicated pre-prod exists for founder sites. ./ship remains available (preprod → main merge + full ceremony) and is likewise fleet-gated.
1bis. ./ship pipeline detail (numbered steps)
./ship <tenant> executes the following steps in order:
- Flag parsing:
--allow-dirty(bypasses the dirty guard),--no-close(skips the post-ship step); remaining flags are forwarded to the dispatcher. - Blocking dirty guard: checks working tree cleanliness — immediate abort if dirty (unless
--allow-dirtyis set). - EXIT trap: return to the
preprodbranch is guaranteed even on error; a failed checkout is reported loudly (to avoid being left onmain). - Root artifact cleanup: removes output directories left by previous builds with root ownership that would block writes.
- Hub drift check (blocking): verifies the hub schema — no bypass possible. Followed where applicable by the tenant prod database drift check.
preprod → mainmerge: checkout main, pull, merge with theirs strategy, push.- Migrations: informational display of pending
.sqlfiles (see §1ter) — non-blocking, not auto-applied. - Agent audit: checks fleet agent consistency — non-blocking.
- Prod deployment: calls the deployment dispatcher (see §2).
- Prod smoke: process environment check, then HTTP + content/JSON checks.
- Post-ship close: offers to close worksites in
teststatus (skip via--no-close).
--skip-driftasymmetry:./deployaccepts--skip-driftto bypass the drift check in pre-prod (urgent CI hotfix case)../shiphas no equivalent — hub drift is strictly blocking there: a ship to production on a divergent schema is never allowed.
1ter. DB migrations during ship
The ship displays pending .sql files but never applies them automatically. The automatic migration runner has been disabled and no alternative runner is wired in — this is the actual hard constraint. The block is purely informational (non-blocking): it prints the instructions to be executed manually.
A mapping associates each tenant argument with a migration scope, to prevent mixing mothership and tenant migrations:
| Tenant argument | Migration scope |
|---|---|
| mothership / OS runtime | mothership |
| Tenant A (e-commerce store) | tenant-a |
| Tenant B (vape shop) | tenant-b |
| Founder site 1 | dedicated scope |
| Founder personal site | dedicated scope |
| CodeMyShop platform | dedicated scope |
all |
all scopes (special case) |
| other | fallback: scope = argument passed |
Special case all: aggregates all .sql files from every scope, excluding _applied/ and applied/ subdirectories (already-run migrations). Other scopes simply list the files in the relevant directory.
2. Routing of ./deploy and ./ship by tenant
Each tenant argument is routed to a dedicated script or to the generic deployment dispatcher.
| Tenant (argument) | ./deploy → |
./ship → |
|---|---|---|
| mothership (default) | Health lock check, then mothership self-deploy script (--prod --all) |
Same self-deploy script (--prod --all) |
| Founder personal site | Refused — no pre-prod | Generic dispatcher |
| synedre.com | Generic dispatcher → direct PROD | Generic dispatcher ⚠️ — the process environment check does not recognise this tenant (latent bug), which blocks the smoke |
| CodeMyShop | Generic dispatcher → direct PROD (no more pre-prod); smoke enabled | Generic dispatcher |
| CodeMyShop demo | Generic dispatcher → demo staging | — |
| Tenant A (e-commerce) | Generic dispatcher → pre-prod; smoke disabled (pre-prod behind basic authentication) | Generic dispatcher → prod |
| Tenant B (vape shop) | Generic dispatcher → pre-prod; smoke enabled | ⚠️ Falls through to fallback → legacy Ansible script (likely failing; no prod VPS configured for this path) |
| Founder site 1 | Generic dispatcher → PROD; smoke enabled | Generic dispatcher |
| all | 5 background parallel jobs: mothership, CodeMyShop (PROD), Tenant A (pre-prod), Tenant B, Founder site 1. Post-job smoke. ⚠️ The all mode smoke checks the CodeMyShop demo staging, not the prod storefront. |
3 parallel jobs: mothership (--prod --all), founder personal site, CodeMyShop. Smoke on all 3 targets. |
--list / -l |
Dumps the VPS inventory table (see §6) | — |
| other | Fallback: legacy Ansible script → pre-prod | Fallback: legacy Ansible script → prod |
Convergence toward a single dispatcher: following a standardisation effort and a purge of legacy scripts, all remote tenants except the mothership go through the single dispatcher for both
./deployand./ship. The mothership retains its dedicated script because self-deploy is architecturally distinct: local Docker build, with no network transfer to an external VPS.
Smoke inconsistency in
allmode:./deploy alldeploys CodeMyShop directly to production, but the subsequent smoke checks the demo staging environment. Thecodemyshop.comstorefront is not verified in this path — this should be corrected in theallmode smoke loop.
The YAML-driven dispatcher
The standard deployment script handles all remote client projects. It accepts a tenant name, an optional target, and a global flag, along with an optional cache-clearing flag. Its schema specification is maintained in a dedicated document, regularly supplemented with canonical examples covering both runtime variants (containerised and supervised process) as well as multi-target configurations.
Configuration file resolution
At startup, the dispatcher resolves the YAML file to use according to two rules:
- If the designated tenant matches the host system itself, the dispatcher looks for a dedicated configuration file — this branch is documented but remains inert in practice: the corresponding file does not exist on disk, because the host system has its own self-deployment pipeline, separate from the standard dispatcher.
- For any other tenant, the dispatcher loads the
deploy.yamlfile (ordeploy.<target>.yamlif a target is specified) located in the tenant's directory within the projects tree.
Note: The tenant name corresponding to synedre.com (the founding site) is treated as an ordinary tenant and loads its own configuration file — it does not trigger the host-system branch.
Deployment pipeline steps
- Argument parsing: the target flag (with mandatory
=separator), the global flag, and the cache-clearing flag are extracted and validated. - YAML resolution and validation: a Python utility parses the configuration file and emits deployment variables as key-value pairs evaluated by the shell. Any validation error immediately halts the pipeline.
- Standard argument parsing: extraction of the cache-clearing and global deployment indicators.
- Start banner: display of the deployment summary and initialisation of the stopwatch.
- Schema drift check (if the
driftsection is present): comparison between the expected schema and the actual state of the database. - Automatic drift application (if the
driftsection is present): generation and application in a single transaction of missing idempotent DDL statements — table creation and column addition if absent. This step is active by default and runs before any build or reload. It can be temporarily disabled via a dedicated environment variable. - Background hooks: launch of the agent audit as a background task.
- i18n route generation (pre-build) (if the
seed_i18nsection is present): querying the tenant's database via the remote connection to read localised route segments, then writing the JSON file consumed by the internationalisation module at build time. This step is non-blocking: if the database is unreachable, the build falls back to hardcoded values. - Nuxt build: compilation of the front-end application.
- i18n seed (if applicable): injection of translation data.
- Source maps (if the corresponding hook is enabled): upload of source maps.
- Packaging: creation of a compressed archive of the build in the system's temporary directory.
- Background Git push (if the hook is enabled).
- Archive upload: transfer to the client VPS via secure copy.
- Remote reload, dispatched according to the declared variant:
pm2: graceful reload or forced restart of the supervised process.docker: service restart via Docker Compose.
- Recurring task installation (if the
cronsection is present): writing scheduled tasks into the SSH user's crontab on the client VPS (never on the host system). Each entry is delimited by an idempotent marker block: the old block is removed before rewriting. An empty list triggers cleanup of the existing block. Doctrine: recurring tasks run as close as possible to their database. - Background hook synchronisation: waiting on the Git push and the agent audit.
- Health check: availability polling loop for the target URL.
- End banner: display of the summary and total duration.
Configuration file schema
Each client project is described by a YAML file validated by the dedicated Python utility. The sections are as follows:
| Section | Required | Description |
|---|---|---|
name |
Yes | Kebab-case deployment identifier (lowercase letters, digits, hyphens, underscores). |
build |
Yes | Relative path of the local client (required); Node environment (default: production). |
ssh |
Yes | Target host (required); SSH user (default: ubuntu); optional SSH key. |
drift |
No | Declaration of expected tables and columns for schema drift checking and application. |
seed_i18n |
No | Parameters for generating localised routes from the database. |
remote |
Yes | Runtime variant (pm2 or docker) and associated parameters. |
health |
Yes | Health check URL (required); maximum wait timeout (default: 45 s). |
hooks |
No | Optional activation of: agent audit, source map upload, Git push. |
cron |
No | List of recurring tasks to install on the client VPS (entries: key, schedule, command, comment). |
Per-variant consistency guards
The validation utility enforces strict consistency rules depending on the chosen variant:
pm2variant: the supervised application name is required; Docker-specific parameters (container name, subdirectory) are forbidden.dockervariant: the container name is required; PM2-specific parameters are forbidden.
Recurring task validation
Each entry in the cron section is validated individually: the key must be in kebab-case, the schedule must contain exactly five fields in standard crontab format, and the command cannot be empty. A cron section declared empty is nonetheless emitted by the validator — this signal instructs the installer to fully clean up the tenant's task block on the target VPS.
Configuration file example
Below is the structure of a typical configuration file for a project using the PM2 variant:
name: my-project
build:
local_client: codemyshop/tenants/my-project
node_env: production
ssh:
host: <client VPS address>
remote:
variant: pm2
dir: /var/www/codemyshop/app/codemyshop/tenants/my-project
pm2_app: my-project-nuxt
pm2_remote_user: codemyshop
health:
url: https://my-project.com
hooks:
git_push: my-project
Reminder: The SSH host address is never committed in plaintext in a shared file — it is provided via an environment variable or a local configuration file excluded from version control.
Build-host pattern, compression and transfer (no build on the VPS)
The central invariant for remote deployments is as follows: the VPS never compiles. The front-end application bundle is built entirely on the host machine (the mothership), compressed, transferred, extracted on the target VPS, and then the application process is reloaded. The startup banner explicitly displays the BUILD HOST mode to confirm this invariant on every launch.
┌──────────────── HOST MACHINE (mothership) ─────────────────────┐
│ 1. Dependency installation (delta, local cache) │
│ 2. URL invariant smoke tests (blocking) │
│ 3. Front-end application build → .output/ │
│ 4. Compression of .output directory into tar+pigz archive │
└────────────────────────────┬───────────────────────────────────┘
│ secure transfer (scp)
▼
┌──────────────────────── CLIENT VPS ────────────────────────────┐
│ 5. Old .output set aside (rollback point) │
│ 6. Archive extraction (auto-detected decompressor) │
│ 7. Process reload (graceful reload or restart) │
│ 8. If online → purge .output_old otherwise → exit 1 + advice │
└────────────────────────────┬───────────────────────────────────┘
│
▼ HTTP polling until 200 (configurable timeout)
health check
Detailed pipeline steps
- Front-end build: the output directory
.outputis purged (and.nuxtas well if the--cleanflag is set). Dependencies are installed in offline mode from the local cache. A blocking smoke test validates the shape invariants of the product URLs before any build; on failure it immediately cancels the deployment. The build then runs with automatic retry on clean if the incremental compilation fails. - Compression: the archive is created from the
.outputdirectory usingpigzif available on the host, otherwisegzipas a systematic fallback. - Transfer: the archive is copied to the VPS temporary directory via
scp. The local archive is deleted immediately after the transfer. - Docker restart: SSH connection,
.output → .output_oldrotation, archive extraction into the application directory, removal of a native CommonJS module known to cause tree-shaking artifacts, container restart, verification that the container is inrunningstate — otherwise rollback. - PM2 graceful reload:
pm2 reload <app> --update-envpreserves in-flight connections. An optional symbolic link can be recreated pointing to the static files folder to avoid file resolution errors on back-office routes. - PM2 hard restart: used only on first deployment or when the PM2 configuration is explicitly modified —
delete+start+savesequence. - Loop health check: HTTP polling of the main URL until a
200status code is returned, with a configurable maximum wait time (default 45 s). - Per-tenant cron installer: an optional step installs or updates the scheduled tasks specific to each client on the remote VPS.
Rollback strategy
The .output_old directory is retained for the entire duration of the reload. If the reload succeeds, it is purged automatically. On failure — process or container not online — the script only displays the suggested restoration command and exits with code exit 1. Restoration is never executed automatically: the operator must manually replace .output with .output_old. This behavior is identical for PM2 graceful, PM2 hard, and Docker pipelines. The environment variable SHIP_KEEP_ROLLBACK=1 forces retention of .output_old even on success.
Post-deployment smoke: three validation layers
Since early June 2026, the smoke script incorporates two sub-levels of automated verification, complemented by a third visual layer.
-
HTTP checks: every URL for every covered tenant is probed via
curl. A5xxcode is blocking; a4xxcode generates a non-blocking warning. The global loop covers all registered tenants; certain tenants are also probed individually from the ship orchestrator. -
JSON content checks: beyond the HTTP status code, the shape of the JSON returned by the navigation API is structurally validated. This layer was added following an incident (May 2026) where a deployment returned HTTP
200for two days but the JSON response contained an error field — navigation was empty and the footer displayed raw keys. Without this level, SSR data-driven breakages could pass entirely undetected. - Multimodal visual smoke (non-blocking, since June 2026): a verification agent captures a Playwright screenshot of the deployed page and produces a verdict through image analysis. If no visual check configuration is present for a given tenant, the step is cleanly skipped with no latency. Active by default, can be disabled via environment variable.
Environment guardrails before smoke
Remote process environment variable verification
An HTTP 200 code is not sufficient to validate a PM2 deployment. A May 2026 incident demonstrated this: a tenant redeployed without its database connection variables returned 200 on all SSR routes, because the page rendered an empty but syntactically valid skeleton. All data-driven routes (navigation, footer, reviews, internationalisation) were throwing 500 errors silently for two days.
This is why the ship orchestrator runs an SSH environment checker before the smoke: it connects to the remote VPS and inspects the critical variables actually loaded into the environment of the running PM2 process. Return codes: 0 if everything is present, 1 if variables are missing (blocking), 2 if the SSH connection or the PM2 process is unreachable (blocking). Non-PM2 targets (mothership, unaffected environments) receive an immediate exit 0.
Lockfile consistency check before build
Before any deployment of the mothership's main application, a lockfile consistency checker is invoked. It detects two known blocking skews:
- A CSS minification tool version below the required threshold in the lockfile (missing transitive PostCSS dependencies → silently broken build).
- A Nuxt framework version below the required threshold in the project manifest (bundler skew risk).
A non-blocking warning is emitted if Vite version overrides are present in the root configuration. On a blocking error, the script displays an explicit corrective message (lockfile deletion and clean reinstall) and exits with exit 1, cancelling the deployment.
Special case: mothership self-deployment
The mothership cockpit application does not go through the build-host/upload pipeline described above. It is a local self-deployment: the mothership compiles inside its own application container.
Differences from remote tenants
- No pre-production environment since May 2026:
./deployon the mothership application is a direct live-production rebuild of the cockpit, without the branch-merging ceremony of./ship. - Local transfer via
docker cp: a minimal source tar (application code + dependency manifests) is copied into the container. The tar explicitly excludes cache and build directories (.nuxt,.output,node_modules). The performance key is preservation of the.nuxtcache on the container side: it is set aside before the new source code is extracted, then restored, enabling an incremental build rather than a cold build. - Build inside the container: the build command is executed inside the running container. The old server remains active throughout the entire build duration. After the build, the container is gracefully stopped, the new
.outputdirectory is copied to the host, and then the container is recreated with--force-recreateso that environment variables are re-read from the configuration file. - Standalone architecture: the mothership application no longer extends the shared store core. The source tar therefore excludes all store code, significantly reducing the transfer size (approximately 16 MB saved).
- npm fast-path: a
sha256fingerprint of the lockfile is stored inside the container. If the lockfile has not changed since the last deployment, thenpm installstep is skipped entirely. - Build UUID health check: instead of a simple HTTP
200polling, the health check compares the new build's UUID (read from the.outputmanifest on the host side) with the one actually served by the container's health endpoint. This guarantees that the new build is genuinely active, rather than a previous version kept alive by a partial hot-reload. - Opt-in accelerated variant: an environment variable enables an alternative pipeline — build on the host + atomic directory swap inside the container — delivering approximately 35% reduction in total deployment duration.
./shipon the mothership application adds the full ceremony (branch merging, push, agent audit, production smoke, post-ship closure), but ultimately calls the same local deployment script at the end of the process.
Topology Inventory — Central Environment Registry
The system maintains a single central registry of all deployed environments: client VPS instances, application stacks, public domains, and criticality metadata. This registry is the sole authoritative source of truth for topology; any hand-written architecture document may be outdated and carries no authority.
The ./deploy --list command queries this registry and displays a formatted table of all active environments, sorted by descending criticality. Each row contains: the environment identifier, its type (production, staging, infra, legacy, audit), its public domain, and the nature of its stack (Nuxt server-side rendering or e-commerce engine).
Registry Structure
Each registry entry exposes the following columns:
| Field | Role |
|---|---|
| Environment identifier | Primary logical key for the entry |
| Environment type | production / staging / infra / legacy / audit |
| SSH access credentials | Target VPS address, user, and key path — never exposed in plaintext in versioned files |
| Public domain | Public-facing URL of the environment |
| Associated database | References to the DB container, database name, and user; the password is never stored here — only the name of the environment variable carrying it is referenced |
| Web runtime | Web container name, presence of an e-commerce engine, presence of Nuxt rendering |
| Criticality & billing | Operational criticality level, MRR, commercial plan |
| Automatic deployment authorization | Boolean indicating whether an automatic ship is permitted — certain environments require explicit human validation |
| Deployment codename | Identifier to copy when issuing the exact command (see the Before Each Deployment section below) |
| Client reference | Foreign key into the federated client registry (federation introduced in May 2026) |
Fleet Status and Edge Cases
The registry currently holds approximately ten active environments, covering a variety of stacks (Nuxt, e-commerce, infra). Several situations warrant particular attention:
- Orphaned production environments: certain environments carry the production type even though they are dormant (projects with no current commercial activity). They must not be regrouped under the legacy label — their actual type is production.
- Possible duplicate entries: a given project may appear under two distinct rows (for example a production / Nuxt row and a legacy / no Nuxt row) when two generations coexist. Do not merge them.
- Decommissioning: a decommissioned environment is set to
active=0and disappears from active listings. Its directory and secrets are removed; client history is retained in the federated registry.
Before Each Deployment
Before running ./deploy or ./ship, consult the target environment's entry to copy the exact deployment codename. This precaution eliminates any ambiguity about the targeted environment and ensures the command relies on up-to-date registry parameters rather than a memorized value or a potentially outdated architecture document.
Secrets Doctrine — Five Scope Levels
Each secret has a single canonical file. The presence of the same key in two distinct files is treated as a bug to be fixed immediately.
The Five Levels
| # | Scope | Description | Versioned |
|---|---|---|---|
| 1 | Single tenant | Secrets belonging to a single client environment — read by its application container and deployment pipeline | No |
| 2 | Mothership — application core | Secrets for the cockpit and the main Nuxt container | No |
| 3 | Mothership — host scripts | Secrets consumed by automation scripts, cron jobs, and SSH connections to client VPS instances | No |
| 4 | Cross-project | Secrets shared across all projects: AI provider API keys, application encryption key, primary SMTP configuration — stored outside the repository on the host machine | No (outside repo) |
| 5 | Public templates | .env.example files — contain only variable names, never actual values |
Yes |
Load Order and Priority
The main application container loads secret files in the following order: cross-project file → core file → host file. In the event of a duplicate between the core file and the host file, the host file takes priority. Direct consequence: never declare the same variable at both levels.
P0 Anti-Leak Rule
No plaintext secret may appear in a versioned file. When a versioned file must reference a secret, it cites only the name of the environment variable and the .env* file that carries it — never the value. Application examples:
- The deployment pipeline references the name of the variable carrying the database password, not the password itself.
- SMTP and IMAP credentials live in the host file; only their variable names are cited in versioned configuration.
- The central environment registry stores the name of the DB password environment variable — never its value.
Note: The architecture summary view describes three levels (infra / core / tenants). The full doctrine distinguishes five by adding the cross-project level and public templates. In the event of any discrepancy, the detailed doctrine prevails.
Pre-Deployment Commit Rule
A past incident highlighted the following risk: a fix applied locally but never committed can be silently overwritten during a subsequent deployment. The deployment pipeline performs a git checkout of the target directory before pushing files — any locally modified but unversioned file is therefore lost. This incident caused four hours of production downtime.
Guard Behavior
A repository state verification script runs before every operation. It returns three possible states: clean repository, repository with uncommitted changes, or directory outside a repository. Verification is scoped by target: uncommitted changes in a mothership file do not block a tenant deployment, and vice versa.
- During a
./deploy: if uncommitted changes are detected, the system performs a silent automatic commit (standardized, timestamped message) followed by a background push. Exception: if the current branch ismain, the deployment is rejected — no automatic commit is performed on the main branch. - During a
./ship: if uncommitted changes are detected, the operation is blocked with an explicit message prompting the user to commit and relaunch. A bypass flag exists for intentional emergency fixes only.
End-of-Session Hook
A hook triggered at the end of the session also checks repository state and blocks session closure if uncommitted changes remain. The rule is absolute: no completed work remains uncommitted. The agent commits; the human operator never types git add / git commit commands manually.
Reference Files
| Component | Role |
|---|---|
| Pre-production deployment entry point | Orchestrates routing to the correct tenant in pre-production |
| Production deployment entry point | Full production release ceremony, including fleet management and per-environment authorization handling |
| YAML-driven dispatcher | Handles remote deployments (tenants external to the mothership) from a declarative configuration file |
| Deployment helper library | Reusable functions: build, packaging, upload, reload, health check, banner, cron |
| Declarative configuration parser | Reads and validates the deployment configuration file; exposes parameters to scripts (sections: build, SSH, drift detection, internationalization, remote, health, hooks, cron) |
| Mothership self-deployment script | Deploys the main application container internally (in-container build, stop / copy / restart) |
| Accelerated deployment variant | Builds on the host machine then performs an atomic swap — reduces downtime |
| Repository state guard | Verifies repository cleanliness before a deployment or ship (see previous section) |
| Dependency consistency guard | Verifies the absence of drift in the lock file before mothership deployment |
| HTTP smoke test script | Checks HTTP responses and content/JSON after deployment across all active environments |
| Process environment checker | Validates consistency of rendering process environment variables before production smoke tests |
| Declarative configuration schema | Specification of the deployment file format with canonical examples |
| Operational runbook | SSH procedures, process management, Docker, certificate renewal |
| Secrets doctrine | Reference for the five secret scope levels (see dedicated section above) |
| Central environment registry | Single source of truth for topology (see dedicated section above) |
Warning: The historical workflow document is partially outdated. It still describes a pre-production infrastructure and a release pipeline that were decommissioned in May 2026. In the event of any conflict between that document and the actual source code, the source code prevails.