You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Flightdeck needs a standalone container renovation process with a lifecycle deliberately separate from deployment.
deploy resolves app_refs and env_refs, renders a new release tree and manifest, pushes it to each host, and switches current. renovate must not do any of that. It operates only on the Compose projects already present in the active release: pull the images referenced by the existing Compose files and reconcile their containers in place.
The prototype in #162 establishes the separate workflow shape, but currently handles only a required single-app input and cannot reliably detect image changes because it compares the images used by existing containers before those containers are recreated.
Application selection
The same renovate workflow supports three selection modes:
All: when no application selection is provided, renovate every app declared on every target, on every host of those targets.
Single app: when one app name is provided, renovate only that app on every target where it is declared. Other apps on those targets are untouched.
App list: when a list of app names is provided, renovate exactly those apps on every target where each is declared. Apps outside the list are untouched.
Normalize these modes into one selected-app set before creating target jobs. Remove duplicate names. Any explicitly selected app that matches no target is an error rather than a successful no-op. Targets that contain none of the selected apps are filtered out before Tailscale or SSH setup.
For each selected target, host, and app, renovate the app's complete Compose project with docker compose pull followed by docker compose up -d --remove-orphans. This includes every service belonging to that app's Compose project.
Triggers and schedule
Manual dispatch supports all three selection modes: all apps, one app, or an explicit app list.
The full renovation flow must also be schedulable, for example as a nightly run around 03:00. The exact cron expression and its timezone must be documented explicitly because GitHub Actions schedules use UTC.
A scheduled run can use either of these repository-owned policies:
renovate all applications; or
renovate a configured allowlist of applications.
Scheduled events do not receive workflow_dispatch inputs, so the scheduled selection must come from explicit workflow/repository configuration rather than pretending to pass runtime inputs to the cron event. The representation can be a repository variable or an equally visible configuration owned by the calling repository; an empty or explicit all value selects every app, while a non-empty list selects only those apps.
This allows routine nightly renovation for an approved set while leaving excluded applications available for deliberate single-app or list-based manual renovation.
All triggers must converge on the same reusable renovate implementation. Schedule and manual dispatch must not duplicate the SSH, pull, comparison, or notification logic.
Reusable workflow boundary and target matrix
The desired public interface is directory-oriented. An external consumer should call renovate-shared.yml with its targets directory and optional application selection; it should not have to load target YAML, construct a matrix, or flatten every target into hosts, apps, path, and credential-name inputs itself.
Check out the caller repository to read its target manifests.
Check out the called Flightdeck workflow source separately when its Python implementation is needed; do not confuse caller source with job.workflow_repository / job.workflow_sha.
Parse and validate the target manifests, apply the all/single/list application selection, and emit a JSON target matrix.
Fan out one independently visible job per selected target and run the per-target renovate implementation.
Python may own target parsing, validation, filtering, and matrix JSON generation. Matrix fan-out itself remains GitHub Actions orchestration so targets retain separate statuses, logs, parallel execution, and retries.
There is an important secret-boundary constraint that must be resolved explicitly. With the current target schema, each matrix cell contains the names of its SSH, SOPS, and Tailscale secrets. A reusable workflow can only access secrets explicitly passed by its caller, unless the caller uses secrets: inherit. Once the matrix is created inside the called workflow, the caller can no longer resolve secrets[matrix.credentials...] per cell.
Do not pass secret values through ordinary workflow inputs, and do not adopt secrets: inherit merely to hide matrix construction: it exposes the complete caller secret context to the called workflow and weakens the current least-privilege boundary.
Viable credential models to evaluate are:
target-specific GitHub Environments with stable secret names, allowing the internal matrix job to select an environment by target name;
one explicitly passed credential set shared by all targets, only for consumers where that is genuinely the intended security model; or
retaining a thin consumer-owned matrix adapter that explicitly resolves one target's secrets and calls a clearly named per-target reusable executor.
GitHub Environment secrets are not available to private repositories on GitHub Free, so an environment-per-target design cannot be treated as universally compatible without deciding whether that consumer class is still supported.
The final architecture must document this decision and keep the external contract as high-level as the supported credential model permits. The same boundary question applies to deploy.yml / deploy-shared.yml, but changing existing deployment behavior remains outside this renovate issue and should be handled separately.
Image change detection
Before pulling, record the immutable image identity used by every existing Compose service. After docker compose up has recreated any affected containers, record the identities again and compare them per service.
Floating tags such as latest do not identify a change because the tag is the same before and after the pull. Use an image digest when available, falling back to the image ID. A human-readable OCI image version label may be included when present, but correctness must not depend on that optional metadata.
No change means no notification. Pulling an image without changing the container's final image identity is a no-op.
Telegram notification
After successful renovation, send a Telegram notification only when at least one service changed. The notification summarizes each change using the following identity:
target / host / app / service: old digest-or-ID -> new digest-or-ID
Multiple changes from one workflow run may be grouped into one message as long as every changed service remains identifiable. Do not send a success notification for a failed pull or recreate.
Invariants
Do not resolve or download app_refs or env_refs.
Do not decrypt vaults or render configuration.
Do not create a release tree or manifest.
Do not switch the current symlink.
Do not change an image reference recorded in the active Compose configuration; only pull a newer image published behind that existing reference.
Keep deploy and renovate as separate entry points and implementations.
Acceptance criteria
Manual renovate with no app selection processes every declared app on every target host.
Manual renovate with one app processes only that app everywhere it is declared.
Manual renovate with an app list processes exactly the listed apps everywhere they are declared.
A nightly schedule can renovate all apps or a repository-configured app allowlist.
The cron expression and timezone semantics are documented.
Apps excluded from the scheduled allowlist remain runnable manually.
Manual and scheduled triggers reuse the same renovate implementation.
Target discovery, validation, and application filtering have one implementation shared by every trigger.
The reusable workflow boundary and credential model are documented, including private GitHub Free compatibility.
Secret values never travel through ordinary inputs, and the design does not silently broaden access through secrets: inherit.
Any explicitly selected unknown app fails the run with a clear diagnostic.
Targets with no selected apps are filtered out before Tailscale and SSH setup.
Services are recreated only through their existing active-release Compose project.
Changed services report their actual old and new digest or image ID.
An unchanged run is successful and silent in Telegram.
A changed run sends a Telegram summary containing target, host, app, service, and old/new identities.
Tests cover full renovation, single-app renovation, list renovation, scheduled selection, unknown apps, unchanged images, changed images, multiple services, and command failure.
Context
Flightdeck needs a standalone container renovation process with a lifecycle deliberately separate from deployment.
deployresolvesapp_refsandenv_refs, renders a new release tree and manifest, pushes it to each host, and switchescurrent.renovatemust not do any of that. It operates only on the Compose projects already present in the active release: pull the images referenced by the existing Compose files and reconcile their containers in place.The prototype in #162 establishes the separate workflow shape, but currently handles only a required single-app input and cannot reliably detect image changes because it compares the images used by existing containers before those containers are recreated.
Application selection
The same renovate workflow supports three selection modes:
Normalize these modes into one selected-app set before creating target jobs. Remove duplicate names. Any explicitly selected app that matches no target is an error rather than a successful no-op. Targets that contain none of the selected apps are filtered out before Tailscale or SSH setup.
For each selected target, host, and app, renovate the app's complete Compose project with
docker compose pullfollowed bydocker compose up -d --remove-orphans. This includes every service belonging to that app's Compose project.Triggers and schedule
Manual dispatch supports all three selection modes: all apps, one app, or an explicit app list.
The full renovation flow must also be schedulable, for example as a nightly run around 03:00. The exact cron expression and its timezone must be documented explicitly because GitHub Actions schedules use UTC.
A scheduled run can use either of these repository-owned policies:
Scheduled events do not receive
workflow_dispatchinputs, so the scheduled selection must come from explicit workflow/repository configuration rather than pretending to pass runtime inputs to the cron event. The representation can be a repository variable or an equally visible configuration owned by the calling repository; an empty or explicitallvalue selects every app, while a non-empty list selects only those apps.This allows routine nightly renovation for an approved set while leaving excluded applications available for deliberate single-app or list-based manual renovation.
All triggers must converge on the same reusable renovate implementation. Schedule and manual dispatch must not duplicate the SSH, pull, comparison, or notification logic.
Reusable workflow boundary and target matrix
The desired public interface is directory-oriented. An external consumer should call
renovate-shared.ymlwith its targets directory and optional application selection; it should not have to load target YAML, construct a matrix, or flatten every target intohosts,apps,path, and credential-name inputs itself.Conceptually, the call should stay close to:
The shared workflow should own the orchestration:
job.workflow_repository/job.workflow_sha.Python may own target parsing, validation, filtering, and matrix JSON generation. Matrix fan-out itself remains GitHub Actions orchestration so targets retain separate statuses, logs, parallel execution, and retries.
There is an important secret-boundary constraint that must be resolved explicitly. With the current target schema, each matrix cell contains the names of its SSH, SOPS, and Tailscale secrets. A reusable workflow can only access secrets explicitly passed by its caller, unless the caller uses
secrets: inherit. Once the matrix is created inside the called workflow, the caller can no longer resolvesecrets[matrix.credentials...]per cell.Do not pass secret values through ordinary workflow inputs, and do not adopt
secrets: inheritmerely to hide matrix construction: it exposes the complete caller secret context to the called workflow and weakens the current least-privilege boundary.Viable credential models to evaluate are:
GitHub Environment secrets are not available to private repositories on GitHub Free, so an environment-per-target design cannot be treated as universally compatible without deciding whether that consumer class is still supported.
The final architecture must document this decision and keep the external contract as high-level as the supported credential model permits. The same boundary question applies to
deploy.yml/deploy-shared.yml, but changing existing deployment behavior remains outside this renovate issue and should be handled separately.Image change detection
Before pulling, record the immutable image identity used by every existing Compose service. After
docker compose uphas recreated any affected containers, record the identities again and compare them per service.Floating tags such as
latestdo not identify a change because the tag is the same before and after the pull. Use an image digest when available, falling back to the image ID. A human-readable OCI image version label may be included when present, but correctness must not depend on that optional metadata.No change means no notification. Pulling an image without changing the container's final image identity is a no-op.
Telegram notification
After successful renovation, send a Telegram notification only when at least one service changed. The notification summarizes each change using the following identity:
target / host / app / service: old digest-or-ID -> new digest-or-IDMultiple changes from one workflow run may be grouped into one message as long as every changed service remains identifiable. Do not send a success notification for a failed pull or recreate.
Invariants
app_refsorenv_refs.currentsymlink.Acceptance criteria
secrets: inherit.Related