fix(approach-c): full E2E success on marcelle - byte-identical templates + core-only recreate
This session completed Approach C end-to-end on marcelle (status=COMPLETED,
mkdocs untouched, idempotent on re-run). Four fixes landed:
1. template-engine.ts: dropped nginx/conf.d/*.hbs (default, api, services)
from renderAllTemplates AND renderAllTemplatesInMemory. The new
prod-style docker-compose.yml.hbs does NOT mount conf.d/ into the
nginx container ("Note: conf.d is NOT mounted (configs are generated
at startup from templates)" — nginx confs are baked into the nginx
Docker image). Writing them was a no-op orphan that showed up as 3
"modified" lines in preview unnecessarily.
Same reason removed nginx/nginx.conf from staticFiles.
2. templates/configs/{pangolin/resources.yml,prometheus/prometheus.yml,
grafana/datasources/datasources.yml}.hbs: synced byte-identical to
canonical changemaker.lite/configs/*. These ARE mounted into pangolin
tunnel + prometheus + grafana respectively. Preview now reports
"unchanged" for them on install.sh tenants.
3. templates/docker-compose.yml.hbs: dropped the CCP-tenant header
comment, making the template now BYTE-IDENTICAL (58907 bytes) to
canonical changemaker.lite/docker-compose.prod.yml. Even a 1-byte
comment difference caused docker compose to compute new config hashes
for every service, triggering full-stack recreates (including
ccp-agent — the Phase 6 self-destruct trap from upgrade.sh).
4. upgrade.service.ts:runReleaseUpgrade — composeUp now restricted to
core app services [api, admin, media-api, nginx] (same set as
image-upgrade.sh). Unscoped composeUp would recreate ccp-agent
mid-apply and orphan the runner. Until Approach C inherits the
deferred-ccp-agent-restart pattern from upgrade.sh, this restriction
keeps the apply path safe. Limitation: brand-new services in a
release won't auto-deploy via Approach C alone — operator must
follow with Approach A (full upgrade.sh) to pick them up.
E2E verification on marcelle:
- Apply: status=COMPLETED, duration<10s.
- mkdocs.yml md5 unchanged (38810d9df8b4258ad46a6739232cf88a).
- mkdocs/docs file count unchanged (242).
- docker-compose.yml now byte-identical to canonical (58907 bytes).
- app + api public sites: 200 both.
- Re-preview: ALL 10 files show "unchanged" — true idempotency.
Phase 6 acceptance gate met. Approach C now fully operational on the
install.sh fleet.
Bunker Admin
This commit is contained in:
parent
8af11af720
commit
5331cdcc67
@ -243,12 +243,15 @@ export async function renderAllTemplates(context: TemplateContext, outputDir: st
|
|||||||
|
|
||||||
const templatesDir = path.resolve(__dirname, '../..', 'templates');
|
const templatesDir = path.resolve(__dirname, '../..', 'templates');
|
||||||
|
|
||||||
|
// Templates that produce on-disk files actually consumed by tenant containers.
|
||||||
|
// nginx/conf.d/* templates removed 2026-05-23: the new (prod-style)
|
||||||
|
// docker-compose.yml does NOT mount conf.d/ into the nginx container
|
||||||
|
// ("Note: conf.d is NOT mounted (configs are generated at startup from
|
||||||
|
// templates)" — nginx confs are baked into the nginx Docker image).
|
||||||
|
// Writing them was a no-op orphan.
|
||||||
const templateFiles = [
|
const templateFiles = [
|
||||||
{ template: 'docker-compose.yml.hbs', output: 'docker-compose.yml' },
|
{ template: 'docker-compose.yml.hbs', output: 'docker-compose.yml' },
|
||||||
{ template: 'env.hbs', output: '.env' },
|
{ template: 'env.hbs', output: '.env' },
|
||||||
{ template: 'nginx/conf.d/default.conf.hbs', output: 'nginx/conf.d/default.conf' },
|
|
||||||
{ template: 'nginx/conf.d/api.conf.hbs', output: 'nginx/conf.d/api.conf' },
|
|
||||||
{ template: 'nginx/conf.d/services.conf.hbs', output: 'nginx/conf.d/services.conf' },
|
|
||||||
{ template: 'configs/pangolin/resources.yml.hbs', output: 'configs/pangolin/resources.yml' },
|
{ template: 'configs/pangolin/resources.yml.hbs', output: 'configs/pangolin/resources.yml' },
|
||||||
{ template: 'configs/prometheus/prometheus.yml.hbs', output: 'configs/prometheus/prometheus.yml' },
|
{ template: 'configs/prometheus/prometheus.yml.hbs', output: 'configs/prometheus/prometheus.yml' },
|
||||||
{ template: 'configs/grafana/datasources/datasources.yml.hbs', output: 'configs/grafana/datasources/datasources.yml' },
|
{ template: 'configs/grafana/datasources/datasources.yml.hbs', output: 'configs/grafana/datasources/datasources.yml' },
|
||||||
@ -272,9 +275,10 @@ export async function renderAllTemplates(context: TemplateContext, outputDir: st
|
|||||||
logger.debug(`Rendered ${template} → ${outputPath}`);
|
logger.debug(`Rendered ${template} → ${outputPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy static files (no templating needed)
|
// Copy static files (no templating needed). nginx/nginx.conf removed
|
||||||
|
// 2026-05-23 — same reason as nginx/conf.d/* in templateFiles above
|
||||||
|
// (nginx image bakes its own; on-disk file is an orphan).
|
||||||
const staticFiles = [
|
const staticFiles = [
|
||||||
'nginx/nginx.conf',
|
|
||||||
'configs/prometheus/alerts.yml',
|
'configs/prometheus/alerts.yml',
|
||||||
'configs/alertmanager/alertmanager.yml',
|
'configs/alertmanager/alertmanager.yml',
|
||||||
'configs/grafana/dashboards/dashboards.yml',
|
'configs/grafana/dashboards/dashboards.yml',
|
||||||
@ -311,12 +315,15 @@ export async function renderAllTemplatesInMemory(
|
|||||||
const templatesDir = path.resolve(__dirname, '../..', 'templates');
|
const templatesDir = path.resolve(__dirname, '../..', 'templates');
|
||||||
const result: Array<{ relativePath: string; content: string }> = [];
|
const result: Array<{ relativePath: string; content: string }> = [];
|
||||||
|
|
||||||
|
// Templates that produce on-disk files actually consumed by tenant containers.
|
||||||
|
// nginx/conf.d/* templates removed 2026-05-23: the new (prod-style)
|
||||||
|
// docker-compose.yml does NOT mount conf.d/ into the nginx container
|
||||||
|
// ("Note: conf.d is NOT mounted (configs are generated at startup from
|
||||||
|
// templates)" — nginx confs are baked into the nginx Docker image).
|
||||||
|
// Writing them was a no-op orphan.
|
||||||
const templateFiles = [
|
const templateFiles = [
|
||||||
{ template: 'docker-compose.yml.hbs', output: 'docker-compose.yml' },
|
{ template: 'docker-compose.yml.hbs', output: 'docker-compose.yml' },
|
||||||
{ template: 'env.hbs', output: '.env' },
|
{ template: 'env.hbs', output: '.env' },
|
||||||
{ template: 'nginx/conf.d/default.conf.hbs', output: 'nginx/conf.d/default.conf' },
|
|
||||||
{ template: 'nginx/conf.d/api.conf.hbs', output: 'nginx/conf.d/api.conf' },
|
|
||||||
{ template: 'nginx/conf.d/services.conf.hbs', output: 'nginx/conf.d/services.conf' },
|
|
||||||
{ template: 'configs/pangolin/resources.yml.hbs', output: 'configs/pangolin/resources.yml' },
|
{ template: 'configs/pangolin/resources.yml.hbs', output: 'configs/pangolin/resources.yml' },
|
||||||
{ template: 'configs/prometheus/prometheus.yml.hbs', output: 'configs/prometheus/prometheus.yml' },
|
{ template: 'configs/prometheus/prometheus.yml.hbs', output: 'configs/prometheus/prometheus.yml' },
|
||||||
{ template: 'configs/grafana/datasources/datasources.yml.hbs', output: 'configs/grafana/datasources/datasources.yml' },
|
{ template: 'configs/grafana/datasources/datasources.yml.hbs', output: 'configs/grafana/datasources/datasources.yml' },
|
||||||
@ -334,9 +341,9 @@ export async function renderAllTemplatesInMemory(
|
|||||||
result.push({ relativePath: output, content: rendered });
|
result.push({ relativePath: output, content: rendered });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read static files into memory
|
// Read static files into memory. nginx/nginx.conf removed 2026-05-23 —
|
||||||
|
// same orphan reason as in renderAllTemplates above.
|
||||||
const staticFiles = [
|
const staticFiles = [
|
||||||
'nginx/nginx.conf',
|
|
||||||
'configs/prometheus/alerts.yml',
|
'configs/prometheus/alerts.yml',
|
||||||
'configs/alertmanager/alertmanager.yml',
|
'configs/alertmanager/alertmanager.yml',
|
||||||
'configs/grafana/dashboards/dashboards.yml',
|
'configs/grafana/dashboards/dashboards.yml',
|
||||||
|
|||||||
@ -651,14 +651,24 @@ async function runReleaseUpgrade(
|
|||||||
});
|
});
|
||||||
await driver.composePull(instance.basePath, instance.composeProject);
|
await driver.composePull(instance.basePath, instance.composeProject);
|
||||||
|
|
||||||
// Phase 4: recreate services
|
// Phase 4: recreate services. Restricted to core app services (api,
|
||||||
|
// admin, media-api, nginx) — same set as scripts/image-upgrade.sh.
|
||||||
|
// Calling unscoped composeUp would recreate ccp-agent too, severing the
|
||||||
|
// CCP↔agent connection mid-apply and orphaning the script (same trap as
|
||||||
|
// upgrade.sh Phase 6 self-destruct fixed in v2.10.2). Until we mirror
|
||||||
|
// upgrade.sh's deferred-ccp-agent-restart pattern here, stick to the
|
||||||
|
// explicit service list. Limitation: Approach C will not pick up
|
||||||
|
// brand-new services in a release until either (a) operator runs full
|
||||||
|
// upgrade.sh (Approach A) afterwards, or (b) this is upgraded to the
|
||||||
|
// deferred-restart pattern.
|
||||||
|
const coreServices = ['api', 'admin', 'media-api', 'nginx'];
|
||||||
await updateStatus({
|
await updateStatus({
|
||||||
currentPhase: 4,
|
currentPhase: 4,
|
||||||
phaseName: 'Recreate Services',
|
phaseName: 'Recreate Services',
|
||||||
percentage: 80,
|
percentage: 80,
|
||||||
progressMessage: 'Recreating services with new orchestration...',
|
progressMessage: `Recreating core services (${coreServices.join(', ')})...`,
|
||||||
});
|
});
|
||||||
await driver.composeUp(instance.basePath, instance.composeProject);
|
await driver.composeUp(instance.basePath, instance.composeProject, coreServices);
|
||||||
|
|
||||||
// Phase 5: verify (best-effort; soft warnings only)
|
// Phase 5: verify (best-effort; soft warnings only)
|
||||||
await updateStatus({
|
await updateStatus({
|
||||||
|
|||||||
@ -4,7 +4,7 @@ datasources:
|
|||||||
- name: Prometheus
|
- name: Prometheus
|
||||||
type: prometheus
|
type: prometheus
|
||||||
access: proxy
|
access: proxy
|
||||||
url: http://{{containerPrefix}}-prometheus:9090
|
url: http://prometheus-changemaker:9090
|
||||||
isDefault: true
|
isDefault: true
|
||||||
editable: true
|
editable: true
|
||||||
jsonData:
|
jsonData:
|
||||||
|
|||||||
@ -1,116 +1,155 @@
|
|||||||
# Pangolin Resources — Instance: {{name}}
|
# Pangolin Resource Definitions
|
||||||
# All resources route through nginx for consistent subdomain handling
|
# All resources route through Nginx (port 80) by default
|
||||||
|
# Newt tunnel → Nginx (port 80) → Backend containers (various ports)
|
||||||
|
#
|
||||||
|
# target_ip: the hostname/IP that Newt sends traffic to (default: nginx)
|
||||||
|
# target_port: the port on the target host (default: 80)
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
# ─── Always-On Resources ──────────────────────────────────
|
# Required services (fail if down)
|
||||||
|
- subdomain: app
|
||||||
|
name: Admin GUI
|
||||||
|
container: changemaker-v2-admin
|
||||||
|
port: 3000
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: true
|
||||||
|
|
||||||
- name: app
|
- subdomain: api
|
||||||
subdomain: app
|
name: API Server
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
container: changemaker-v2-api
|
||||||
isBaseDomain: false
|
port: 4000
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: true
|
||||||
|
|
||||||
- name: api
|
- subdomain: "" # Root domain
|
||||||
subdomain: api
|
name: Public Site
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
container: mkdocs-site-server-changemaker
|
||||||
isBaseDomain: false
|
port: 80
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: true
|
||||||
|
|
||||||
- name: root
|
# Optional services (warn and skip if down)
|
||||||
subdomain: ""
|
- subdomain: db
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
name: NocoDB
|
||||||
isBaseDomain: true
|
container: changemaker-v2-nocodb
|
||||||
|
port: 8080
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
- name: docs
|
- subdomain: docs
|
||||||
subdomain: docs
|
name: Documentation
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
container: mkdocs-changemaker
|
||||||
isBaseDomain: false
|
port: 8000
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
- name: db
|
- subdomain: code
|
||||||
subdomain: db
|
name: Code Server
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
container: code-server-changemaker
|
||||||
isBaseDomain: false
|
port: 8080
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
- name: mail
|
- subdomain: n8n
|
||||||
subdomain: mail
|
name: Workflows
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
container: n8n-changemaker
|
||||||
isBaseDomain: false
|
port: 5678
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
- name: qr
|
- subdomain: git
|
||||||
subdomain: qr
|
name: Gitea
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
container: gitea-changemaker
|
||||||
isBaseDomain: false
|
port: 3000
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
# ─── Conditional Resources ────────────────────────────────
|
- subdomain: home
|
||||||
|
name: Homepage
|
||||||
|
container: homepage-changemaker
|
||||||
|
port: 3000
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
{{#if enableMedia}}
|
- subdomain: listmonk
|
||||||
- name: media
|
name: Newsletter
|
||||||
subdomain: media
|
container: listmonk-app
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
port: 9000
|
||||||
isBaseDomain: false
|
target_ip: nginx
|
||||||
{{/if}}
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
{{#if enableListmonk}}
|
- subdomain: qr
|
||||||
- name: listmonk
|
name: Mini QR
|
||||||
subdomain: listmonk
|
container: mini-qr
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
port: 8080
|
||||||
isBaseDomain: false
|
target_ip: nginx
|
||||||
{{/if}}
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
{{#if enableGancio}}
|
- subdomain: draw
|
||||||
- name: events
|
name: Excalidraw
|
||||||
subdomain: events
|
container: excalidraw-changemaker
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
port: 80
|
||||||
isBaseDomain: false
|
target_ip: nginx
|
||||||
{{/if}}
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
{{#if enableChat}}
|
- subdomain: vault
|
||||||
- name: chat
|
name: Vaultwarden
|
||||||
subdomain: chat
|
container: vaultwarden-changemaker
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
port: 80
|
||||||
isBaseDomain: false
|
target_ip: nginx
|
||||||
{{/if}}
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
{{#if enableMeet}}
|
- subdomain: mail
|
||||||
- name: meet
|
name: MailHog
|
||||||
subdomain: meet
|
container: mailhog-changemaker
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
port: 8025
|
||||||
isBaseDomain: false
|
target_ip: nginx
|
||||||
{{/if}}
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
{{#if enableMonitoring}}
|
- subdomain: chat
|
||||||
- name: grafana
|
name: Rocket.Chat
|
||||||
subdomain: grafana
|
container: rocketchat-changemaker
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
port: 3000
|
||||||
isBaseDomain: false
|
target_ip: nginx
|
||||||
{{/if}}
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
{{#if enableDevTools}}
|
- subdomain: events
|
||||||
- name: code
|
name: Gancio Events
|
||||||
subdomain: code
|
container: gancio-changemaker
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
port: 13120
|
||||||
isBaseDomain: false
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
- name: git
|
- subdomain: meet
|
||||||
subdomain: git
|
name: Jitsi Meet
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
container: jitsi-web-changemaker
|
||||||
isBaseDomain: false
|
port: 80
|
||||||
|
target_ip: nginx
|
||||||
|
target_port: 80
|
||||||
|
required: false
|
||||||
|
|
||||||
- name: n8n
|
# Monitoring services (auto-detect profile)
|
||||||
subdomain: n8n
|
- subdomain: grafana
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
name: Grafana
|
||||||
isBaseDomain: false
|
container: grafana-changemaker
|
||||||
|
port: 3000
|
||||||
- name: home
|
target_ip: nginx
|
||||||
subdomain: home
|
target_port: 80
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
required: false
|
||||||
isBaseDomain: false
|
profile: monitoring # Auto-detect if monitoring profile active
|
||||||
|
|
||||||
- name: vault
|
|
||||||
subdomain: vault
|
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
|
||||||
isBaseDomain: false
|
|
||||||
|
|
||||||
- name: draw
|
|
||||||
subdomain: draw
|
|
||||||
target: http://{{containerPrefix}}-nginx:80
|
|
||||||
isBaseDomain: false
|
|
||||||
{{/if}}
|
|
||||||
|
|||||||
@ -1,66 +1,61 @@
|
|||||||
# Prometheus — Instance: {{name}}
|
|
||||||
|
|
||||||
global:
|
global:
|
||||||
scrape_interval: 15s
|
scrape_interval: 15s
|
||||||
evaluation_interval: 15s
|
evaluation_interval: 15s
|
||||||
external_labels:
|
external_labels:
|
||||||
monitor: '{{composeProject}}'
|
monitor: 'changemaker-lite'
|
||||||
|
|
||||||
{{#if enableMonitoring}}
|
# Alertmanager configuration
|
||||||
alerting:
|
alerting:
|
||||||
alertmanagers:
|
alertmanagers:
|
||||||
- static_configs:
|
- static_configs:
|
||||||
- targets: ['{{containerPrefix}}-alertmanager:9093']
|
- targets: ['alertmanager:9093']
|
||||||
|
|
||||||
|
# Load rules once and periodically evaluate them
|
||||||
rule_files:
|
rule_files:
|
||||||
- "alerts.yml"
|
- "alerts.yml"
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
|
# Scrape configurations
|
||||||
scrape_configs:
|
scrape_configs:
|
||||||
- job_name: '{{composeProject}}-api'
|
# V2 Unified API Metrics
|
||||||
|
- job_name: 'changemaker-v2-api'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['{{containerPrefix}}-api:4000']
|
- targets: ['changemaker-v2-api:4000']
|
||||||
metrics_path: '/api/metrics'
|
metrics_path: '/api/metrics/internal'
|
||||||
scrape_interval: 10s
|
scrape_interval: 10s
|
||||||
scrape_timeout: 5s
|
scrape_timeout: 5s
|
||||||
|
|
||||||
{{#if enableMedia}}
|
# N8N Metrics (if available)
|
||||||
- job_name: '{{composeProject}}-media-api'
|
- job_name: 'n8n'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['{{containerPrefix}}-media-api:4100']
|
- targets: ['n8n-changemaker:5678']
|
||||||
metrics_path: '/api/metrics'
|
metrics_path: '/metrics'
|
||||||
{{/if}}
|
scrape_interval: 30s
|
||||||
|
|
||||||
- job_name: '{{composeProject}}-redis'
|
# Redis Metrics
|
||||||
|
- job_name: 'redis'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['{{containerPrefix}}-redis-exporter:9121']
|
- targets: ['redis-exporter:9121']
|
||||||
scrape_interval: 15s
|
scrape_interval: 15s
|
||||||
|
|
||||||
{{#if enableMonitoring}}
|
# cAdvisor - Docker container metrics
|
||||||
- job_name: '{{composeProject}}-cadvisor'
|
- job_name: 'cadvisor'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['{{containerPrefix}}-cadvisor:8080']
|
- targets: ['cadvisor:8080']
|
||||||
scrape_interval: 15s
|
scrape_interval: 15s
|
||||||
|
|
||||||
- job_name: '{{composeProject}}-node'
|
# Node Exporter - System metrics
|
||||||
|
- job_name: 'node'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['{{containerPrefix}}-node-exporter:9100']
|
- targets: ['node-exporter:9100']
|
||||||
scrape_interval: 15s
|
scrape_interval: 15s
|
||||||
|
|
||||||
- job_name: '{{composeProject}}-prometheus'
|
# Prometheus self-monitoring
|
||||||
|
- job_name: 'prometheus'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['localhost:9090']
|
- targets: ['localhost:9090']
|
||||||
|
|
||||||
- job_name: '{{composeProject}}-alertmanager'
|
# Alertmanager monitoring
|
||||||
|
- job_name: 'alertmanager'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['{{containerPrefix}}-alertmanager:9093']
|
- targets: ['alertmanager:9093']
|
||||||
scrape_interval: 30s
|
scrape_interval: 30s
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if enableDevTools}}
|
|
||||||
- job_name: '{{composeProject}}-n8n'
|
|
||||||
static_configs:
|
|
||||||
- targets: ['{{containerPrefix}}-n8n:5678']
|
|
||||||
metrics_path: '/metrics'
|
|
||||||
scrape_interval: 30s
|
|
||||||
{{/if}}
|
|
||||||
|
|||||||
@ -1,24 +1,8 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Changemaker Lite v2 — Tenant compose (CCP template)
|
# Changemaker Lite v2 — Production Docker Compose
|
||||||
# Instance: {{name}} ({{slug}})
|
# Pre-built images only. No source code mounts, no build blocks.
|
||||||
# Compose project: {{composeProject}}
|
# Generated from docker-compose.yml by build-release.sh
|
||||||
#
|
|
||||||
# This template is a byte-mirror of changemaker.lite/docker-compose.prod.yml
|
|
||||||
# (modulo this header comment). Approach C (CCP-driven release upgrade)
|
|
||||||
# renders this against the tenant's context and writes the result to the
|
|
||||||
# tenant's filesystem.
|
|
||||||
#
|
|
||||||
# All per-instance variation flows through env-var substitution from the
|
|
||||||
# tenant's .env file (rendered by env.hbs for CCP-provisioned tenants;
|
|
||||||
# kept as-is for install.sh-registered tenants). The CCP controls image
|
|
||||||
# tag selection by writing IMAGE_TAG to the tenant's .env, which the
|
|
||||||
# compose's ${IMAGE_TAG:-latest} substitution then picks up at compose-up.
|
|
||||||
#
|
|
||||||
# To keep this template in sync with canonical docker-compose.prod.yml:
|
|
||||||
# - When a new service is added to changemaker.lite/docker-compose.prod.yml,
|
|
||||||
# copy the same block here verbatim. Handlebars is NOT used in the
|
|
||||||
# compose template itself — all variation is env-var driven.
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user