Files
slp-modular-cms/.gitea/workflows/continuous_integration.yaml
T
SluijsensandClaude Sonnet 5 0feedf9c01
Continuous Integration / config (pull_request) Successful in 12s
Continuous Integration / changes (pull_request) Successful in 22s
Continuous Integration / backend-build (pull_request) Successful in 5m52s
Continuous Integration / vulnerability-scan (pull_request) Successful in 5m6s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m24s
Continuous Integration / backend-test (pull_request) Failing after 8m24s
Continuous Integration / frontend-build (pull_request) Successful in 2m14s
Continuous Integration / frontend-test (pull_request) Successful in 4m28s
Continuous Integration / frontend-lint (pull_request) Successful in 2m5s
Continuous Integration / publish-test (pull_request) Skipped
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-test (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Adds a MariaDB service to backend-test for SlpModularCms.Api.Tests
The new pipeline regression tests boot the real Api host via
WebApplicationFactory, which unconditionally runs the startup database
migration -- unlike every other test project here, which mocks or uses EF
Core InMemory. CI had no MariaDB service at all, so those tests failed on
the connection itself. Adds a mariadb service container to the backend-test
job plus a ConnectionStrings__DefaultConnection override for that step,
which overrides the appsettings.Development.json placeholder via standard
config layering without touching any committed appsettings file or
affecting local test runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWyStNL2ZsjrS7FLd7xvvN
2026-08-02 10:46:25 +02:00

492 lines
23 KiB
YAML

name: Continuous Integration
on:
workflow_dispatch:
inputs:
deploy_test:
description: 'Deploy to Test'
type: boolean
default: false
deploy_production:
description: 'Deploy to Production'
type: boolean
default: false
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [master]
# Reusable settings for this workflow. Change these in one place if the .NET/Node/pnpm version,
# artifact names, or deploy destinations change. The env context is NOT available inside a called
# reusable workflow's `with:` inputs (see the `config` job below, which works around this by passing
# these values through as job outputs to the `deploy-test` / `deploy-production` jobs).
env:
DOTNET_VERSION: '10.0.x'
NODE_VERSION: '20'
PNPM_VERSION: '9'
PUBLISH_RID: linux-arm64
ARTIFACT_NAME_TEST: app-test
ARTIFACT_NAME_PRODUCTION: app-production
DEPLOY_ENVIRONMENT_TEST: test
DEPLOY_ENVIRONMENT_PRODUCTION: production
DEPLOY_PATH_TEST: ${{ vars.DEPLOY_PATH_TEST }}
DEPLOY_PATH_PRODUCTION: ${{ vars.DEPLOY_PATH_PRODUCTION }}
SERVICE_NAME_TEST: ${{ vars.SERVICE_NAME_TEST }}
SERVICE_NAME_PRODUCTION: ${{ vars.SERVICE_NAME_PRODUCTION }}
HEALTH_CHECK_URL_TEST: ${{ vars.HEALTH_CHECK_URL_TEST }}
HEALTH_CHECK_URL_PRODUCTION: ${{ vars.HEALTH_CHECK_URL_PRODUCTION }}
jobs:
# Passes the env: values above through as job outputs, since the env context is unavailable in
# the `with:` block of a job that calls a reusable workflow (see deploy-test / deploy-production).
config:
runs-on: ubuntu-latest
outputs:
artifact_name_test: ${{ steps.set.outputs.artifact_name_test }}
artifact_name_production: ${{ steps.set.outputs.artifact_name_production }}
deploy_environment_test: ${{ steps.set.outputs.deploy_environment_test }}
deploy_environment_production: ${{ steps.set.outputs.deploy_environment_production }}
deploy_path_test: ${{ steps.set.outputs.deploy_path_test }}
deploy_path_production: ${{ steps.set.outputs.deploy_path_production }}
service_name_test: ${{ steps.set.outputs.service_name_test }}
service_name_production: ${{ steps.set.outputs.service_name_production }}
health_check_url_test: ${{ steps.set.outputs.health_check_url_test }}
health_check_url_production: ${{ steps.set.outputs.health_check_url_production }}
steps:
# Gitea has no reliable way to see a past run's workflow_dispatch input values from the UI, so
# this logs them explicitly — the only durable record of what was actually checked for a run.
- name: Log dispatch inputs
if: github.event_name == 'workflow_dispatch'
run: |
echo "Deploy to Test: ${{ github.event.inputs.deploy_test }}"
echo "Deploy to Production: ${{ github.event.inputs.deploy_production }}"
- id: set
run: |
echo "artifact_name_test=${{ env.ARTIFACT_NAME_TEST }}" >> "$GITHUB_OUTPUT"
echo "artifact_name_production=${{ env.ARTIFACT_NAME_PRODUCTION }}" >> "$GITHUB_OUTPUT"
echo "deploy_environment_test=${{ env.DEPLOY_ENVIRONMENT_TEST }}" >> "$GITHUB_OUTPUT"
echo "deploy_environment_production=${{ env.DEPLOY_ENVIRONMENT_PRODUCTION }}" >> "$GITHUB_OUTPUT"
echo "deploy_path_test=${{ env.DEPLOY_PATH_TEST }}" >> "$GITHUB_OUTPUT"
echo "deploy_path_production=${{ env.DEPLOY_PATH_PRODUCTION }}" >> "$GITHUB_OUTPUT"
echo "service_name_test=${{ env.SERVICE_NAME_TEST }}" >> "$GITHUB_OUTPUT"
echo "service_name_production=${{ env.SERVICE_NAME_PRODUCTION }}" >> "$GITHUB_OUTPUT"
echo "health_check_url_test=${{ env.HEALTH_CHECK_URL_TEST }}" >> "$GITHUB_OUTPUT"
echo "health_check_url_production=${{ env.HEALTH_CHECK_URL_PRODUCTION }}" >> "$GITHUB_OUTPUT"
# Path-based skip: keeps the pipeline short when a change only touches one side of the repo.
# fetch-depth: 0 gives paths-filter full local git history to diff against, rather than relying
# on its GitHub-API fallback for missing history, which targets GitHub's REST API and cannot be
# assumed to work against a Gitea remote. Changes under the workflow files themselves count as
# both backend and frontend, so a workflow edit is never left partially untested. A manual
# workflow_dispatch run has no `before` commit to diff against and is always an explicit request
# to run everything, so it force-reports both sides as changed rather than trusting the filter.
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.backend == 'true' }}
frontend: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.frontend == 'true' }}
steps:
# Skipped on workflow_dispatch: there's no `before` commit to diff against, and a manual run
# always force-reports both sides changed anyway (see the outputs above), so the diff would be
# wasted work.
- uses: actions/checkout@v4
if: github.event_name != 'workflow_dispatch'
with:
fetch-depth: 0
- uses: dorny/paths-filter@v3
if: github.event_name != 'workflow_dispatch'
id: filter
with:
filters: |
workflow: &workflow
- '.gitea/workflows/**'
backend:
- *workflow
- 'src/**'
- 'tests/**'
- '*.sln'
- '**/*.csproj'
frontend:
- *workflow
- 'frontend/**'
# --- Gate 1: backend build ---
backend-build:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore and build (Release)
run: dotnet build SlpModularCms.sln -c Release
# --- Gate 2: backend tests ---
backend-test:
needs: [changes, backend-build]
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
# Added for SlpModularCms.Api.Tests (slpsoftware-api feature, NFR-CS-01): its pipeline
# regression tests boot the real Api host via WebApplicationFactory, which unconditionally
# runs CmsHost.ConfigurePipeline's startup migration (MigrateCoreDatabase) -- unlike every
# other test project here, which mocks/uses EF Core InMemory and never touches a real
# database. Without a real, reachable MariaDB, those tests fail on the connection itself
# before any assertion runs.
services:
mariadb:
image: mariadb:11
env:
MARIADB_ROOT_PASSWORD: ci_test_password
MARIADB_DATABASE: SlpModularCms
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping --silent"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Test (Release)
env:
# Overrides the placeholder in SlpModularCms.Api/appsettings.Development.json for this
# CI run only -- ASP.NET Core's configuration layering applies environment variables
# after appsettings.*.json, so this reaches SlpModularCms.Api.Tests' WebApplicationFactory
# without touching any committed appsettings file.
ConnectionStrings__DefaultConnection: "Server=127.0.0.1;Port=3306;Database=SlpModularCms;Uid=root;Pwd=ci_test_password"
run: dotnet test SlpModularCms.sln -c Release
# --- Gate 3: vulnerability scan ---
# `dotnet list package --vulnerable` always exits 0, even when it reports vulnerabilities, so the
# step greps its own output and fails deliberately (FR-22, D-12, OPEN-03 — closed by pinning
# Microsoft.OpenApi and System.Security.Cryptography.Xml in the relevant .csproj files).
vulnerability-scan:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Check for vulnerable packages
run: |
OUTPUT=$(dotnet list SlpModularCms.sln package --vulnerable --include-transitive 2>&1)
echo "$OUTPUT"
if echo "$OUTPUT" | grep -q "has the following vulnerable packages"; then
echo "Vulnerable packages detected — see above."
exit 1
fi
frontend-prepare:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Get pnpm store directory
id: pnpm-store
working-directory: frontend
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
- name: Install dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
# --- Gate 4: frontend build ---
# Validates the frontend compiles on its own, independent of any environment. The actual
# per-environment deployable artifact is produced later by each publish job's own "Build admin
# frontend" step (publish-test / publish-production, below), which builds frontend/ again with
# that environment's Vite variables set and copies the result into wwwroot/admin before
# `dotnet publish` runs — see that step's comment for why this isn't an MSBuild target instead.
frontend-build:
needs: [changes, frontend-prepare]
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Get pnpm store directory
id: pnpm-store
working-directory: frontend
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
- name: Install dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Build
working-directory: frontend
run: pnpm run build
# --- Gate 5: frontend tests ---
frontend-test:
needs: [changes, frontend-prepare]
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Get pnpm store directory
id: pnpm-store
working-directory: frontend
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
- name: Install dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Unit tests
working-directory: frontend
run: pnpm run test
# --- Gate 6: frontend lint / format-check ---
frontend-lint:
needs: [changes, frontend-prepare]
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Get pnpm store directory
id: pnpm-store
working-directory: frontend
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
- name: Install dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Lint
working-directory: frontend
run: pnpm run lint
# Publishes the Api project for the test environment. The admin SPA is built by this job's own
# "Build admin frontend" step below, ahead of `dotnet publish` — see that step's comment for why.
#
# REF-U5-01: the Umami-origin drift check this gate performs exists because U3's own startup check
# (BR-U3-22) can't work — the backend can never see VITE_UMAMI_WEBSITE_ID at runtime. It compares
# two CI-time values instead: the frontend's Umami script origin, and a Gitea variable that MUST be
# kept in sync with the host's real SecurityHeaders__AllowedScriptOrigins__0 env var (D-16). This
# gate cannot see the actual runtime CSP configuration; it only catches drift between the frontend
# build and what this variable claims the CSP allows.
publish-test:
needs: [changes, backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint]
# always() bypasses the automatic skip-cascade from a gate job that was itself skipped (because
# its side of the repo didn't change). Gitea Actions only implements always() from the GitHub
# Actions status-check functions — success()/failure()/cancelled() are not supported — so each
# gate's outcome is checked explicitly via needs.<job>.result instead: a skipped gate is fine,
# but a gate that actually ran and failed or was cancelled still blocks the publish. The
# changes.outputs check makes sure there's something to publish at all: a run that touched
# neither backend nor frontend has nothing new to deploy. The final line skips this build on a
# manual workflow_dispatch run that didn't check deploy_test — building and uploading a test
# artifact nobody is going to deploy is wasted work; push/PR runs are unaffected.
if: |
always() &&
needs.changes.result == 'success' &&
needs.backend-build.result != 'failure' && needs.backend-build.result != 'cancelled' &&
needs.backend-test.result != 'failure' && needs.backend-test.result != 'cancelled' &&
needs.vulnerability-scan.result != 'failure' && needs.vulnerability-scan.result != 'cancelled' &&
needs.frontend-build.result != 'failure' && needs.frontend-build.result != 'cancelled' &&
needs.frontend-test.result != 'failure' && needs.frontend-test.result != 'cancelled' &&
needs.frontend-lint.result != 'failure' && needs.frontend-lint.result != 'cancelled' &&
(needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true') &&
(github.event_name != 'workflow_dispatch' || github.event.inputs.deploy_test == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Verify Umami origin is permitted (REF-U5-01)
if: ${{ vars.VITE_UMAMI_SCRIPT_URL != '' }}
run: |
ORIGIN=$(echo "${{ vars.VITE_UMAMI_SCRIPT_URL }}" | sed -E 's#^(https?://[^/]+).*#\1#')
if ! echo ",${{ vars.SECURITY_ALLOWED_SCRIPT_ORIGINS_TEST }}," | grep -qF ",$ORIGIN,"; then
echo "Umami origin '$ORIGIN' is not present in the SECURITY_ALLOWED_SCRIPT_ORIGINS_TEST Gitea variable."
echo "Fix the variable, or the test environment's SecurityHeaders CSP config it must mirror (D-16)."
exit 1
fi
# Builds the admin SPA and lands it in wwwroot/admin *before* dotnet publish runs, as its own
# step rather than an MSBuild target hooked to Build/Publish. That was tried and reliably
# failed two different ways (see SlpModularCms.Api.csproj's comment on the topic): the SDK's
# wwwroot static-web-asset item set is fixed at project evaluation time, before any target
# runs, so files a target creates afterward never make it into the publish output; and forcing
# them in as an explicit Content item collided with the SDK's own static-web-asset resolution.
# Physically existing on disk before dotnet publish/build ever runs is the only thing that
# worked, verified locally against a clean obj/bin and a fresh node_modules.
- name: Build admin frontend
working-directory: frontend
env:
VITE_APP_ENV: test
VITE_UMAMI_SCRIPT_URL: ${{ vars.VITE_UMAMI_SCRIPT_URL }}
VITE_UMAMI_WEBSITE_ID: ${{ vars.VITE_UMAMI_WEBSITE_ID_TEST }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
run: |
pnpm install --frozen-lockfile
pnpm build
mkdir -p ../src/SlpModularCms.Api/wwwroot/admin
cp -r dist/. ../src/SlpModularCms.Api/wwwroot/admin/
- name: Publish (test)
working-directory: src/SlpModularCms.Api
run: >
dotnet publish -c Release -r ${{ env.PUBLISH_RID }} --self-contained false
-o ${{ github.workspace }}/${{ env.ARTIFACT_NAME_TEST }}
- name: Upload publish artifact
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_NAME_TEST }}
path: ${{ env.ARTIFACT_NAME_TEST }}
retention-days: 1
# Same as publish-test, but only for a manual workflow_dispatch run with deploy_production set —
# a distinct job rather than a parameterized reuse of publish-test, because VITE_APP_ENV is a
# Vite build-time value: one dist/ bundle cannot be tagged as both 'test' and 'production' (FR-05).
publish-production:
needs: [changes, backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint]
# See publish-test's comment above on why this checks needs.<job>.result explicitly instead of
# failure()/cancelled(), which Gitea Actions doesn't support.
if: |
always() &&
needs.changes.result == 'success' &&
needs.backend-build.result != 'failure' && needs.backend-build.result != 'cancelled' &&
needs.backend-test.result != 'failure' && needs.backend-test.result != 'cancelled' &&
needs.vulnerability-scan.result != 'failure' && needs.vulnerability-scan.result != 'cancelled' &&
needs.frontend-build.result != 'failure' && needs.frontend-build.result != 'cancelled' &&
needs.frontend-test.result != 'failure' && needs.frontend-test.result != 'cancelled' &&
needs.frontend-lint.result != 'failure' && needs.frontend-lint.result != 'cancelled' &&
github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true' &&
(needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Verify Umami origin is permitted (REF-U5-01)
if: ${{ vars.VITE_UMAMI_SCRIPT_URL != '' }}
run: |
ORIGIN=$(echo "${{ vars.VITE_UMAMI_SCRIPT_URL }}" | sed -E 's#^(https?://[^/]+).*#\1#')
if ! echo ",${{ vars.SECURITY_ALLOWED_SCRIPT_ORIGINS_PRODUCTION }}," | grep -qF ",$ORIGIN,"; then
echo "Umami origin '$ORIGIN' is not present in the SECURITY_ALLOWED_SCRIPT_ORIGINS_PRODUCTION Gitea variable."
echo "Fix the variable, or the production environment's SecurityHeaders CSP config it must mirror (D-16)."
exit 1
fi
# See publish-test's "Build admin frontend" step for why this runs as its own step ahead of
# dotnet publish rather than an MSBuild target.
- name: Build admin frontend
working-directory: frontend
env:
VITE_APP_ENV: production
VITE_UMAMI_SCRIPT_URL: ${{ vars.VITE_UMAMI_SCRIPT_URL }}
VITE_UMAMI_WEBSITE_ID: ${{ vars.VITE_UMAMI_WEBSITE_ID_PRODUCTION }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
run: |
pnpm install --frozen-lockfile
pnpm build
mkdir -p ../src/SlpModularCms.Api/wwwroot/admin
cp -r dist/. ../src/SlpModularCms.Api/wwwroot/admin/
- name: Publish (production)
working-directory: src/SlpModularCms.Api
run: >
dotnet publish -c Release -r ${{ env.PUBLISH_RID }} --self-contained false
-o ${{ github.workspace }}/${{ env.ARTIFACT_NAME_PRODUCTION }}
- name: Upload publish artifact
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_NAME_PRODUCTION }}
path: ${{ env.ARTIFACT_NAME_PRODUCTION }}
retention-days: 1
# Automatic test deploy on every push to master (unchanged — a merge should reach test without
# extra steps). A manual workflow_dispatch run only deploys to test when deploy_test is checked,
# since a dispatch run is often just re-running CI/production, not a new test build (FR-03, D-01,
# D-09).
deploy-test:
needs: [publish-test, config]
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_test == 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/master')
uses: ./.gitea/workflows/deploy-scp.yaml
secrets: inherit
with:
artifact_name: ${{ needs.config.outputs.artifact_name_test }}
environment: ${{ needs.config.outputs.deploy_environment_test }}
deploy_path: ${{ needs.config.outputs.deploy_path_test }}
service_name: ${{ needs.config.outputs.service_name_test }}
health_check_url: ${{ needs.config.outputs.health_check_url_test }}
run_db_backup: false
# Production deploy ONLY on an explicit workflow_dispatch run with deploy_production checked
# (FR-04, D-09) — never reachable from a plain push to master, so pushing cannot deploy production
# under any circumstance.
deploy-production:
needs: [publish-production, config]
if: github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true'
uses: ./.gitea/workflows/deploy-scp.yaml
secrets: inherit
with:
artifact_name: ${{ needs.config.outputs.artifact_name_production }}
environment: ${{ needs.config.outputs.deploy_environment_production }}
deploy_path: ${{ needs.config.outputs.deploy_path_production }}
service_name: ${{ needs.config.outputs.service_name_production }}
health_check_url: ${{ needs.config.outputs.health_check_url_production }}
run_db_backup: true