This guide provides a comprehensive overview of all reusable GitHub workflows in this repository. Each workflow is designed to be called from your repository's workflows to standardize CI/CD processes across projects.
- Allow Deploys
- Block Deploys
- Code Coverage (Kotlin)
- Component Build
- Component Deploy
- Component Initialize
- Component Test (Kotlin)
- Deploy Kotlin
- Deploy Kotlin V2 (Service Repo)
- Publish Tech Docs
- Pull Request Kotlin
- Pull Request React (Bun)
- Pull Request React (pnpm)
- Rollback
- SonarCloud Analysis
- Track Pending Release
- PR Digest
File: allow-deploys.yml
Purpose: Enables a specified workflow to allow deployments to proceed.
- Enables the specified workflow using GitHub CLI
- Useful for re-enabling deployment workflows after they've been blocked
| Input | Required | Default | Description |
|---|---|---|---|
workflow |
Yes | - | Workflow (filename or name) to allow, e.g. "deploy-production.yml" |
| Secret | Required | Description |
|---|---|---|
ADMIN_PAT |
Yes | GitHub PAT with workflow permissions |
jobs:
allow-deploys:
uses: monta-app/github-workflows/.github/workflows/allow-deploys.yml@main
with:
workflow: "deploy-production.yml"
secrets:
ADMIN_PAT: ${{ secrets.PAT }}File: block-deploys.yml
Purpose: Disables a specified workflow and cancels any in-progress jobs to prevent deployments.
- Disables the specified workflow using GitHub CLI
- Waits for any in-progress jobs to start
- Cancels all in-progress jobs for the specified workflow
- Useful for preventing deployments during incidents or maintenance
| Input | Required | Default | Description |
|---|---|---|---|
workflow |
Yes | - | Workflow (filename or name) to block, e.g. "deploy-production.yml" |
| Secret | Required | Description |
|---|---|---|
ADMIN_PAT |
Yes | GitHub PAT with workflow permissions |
jobs:
block-deploys:
uses: monta-app/github-workflows/.github/workflows/block-deploys.yml@main
with:
workflow: "deploy-production.yml"
secrets:
ADMIN_PAT: ${{ secrets.PAT }}File: code-coverage-kotlin.yml
Purpose: Runs tests with code coverage reporting for Kotlin projects and pushes metrics to Prometheus.
- Validates service name format (must be kebab-case)
- Connects to Tailscale VPN
- Sets up Java environment
- Runs tests with Kover coverage reporting
- Counts lines of code with
cloc - Pushes coverage metrics to Prometheus
| Input | Required | Default | Description |
|---|---|---|---|
service-name |
Yes | - | Project name in kebab-case format (e.g., "my-service") |
runner-size |
No | "normal" | Runner size: "normal" or "large" |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
java-version |
No | "21" | Java version to use |
gradle-module |
No | - | Gradle module name for multi-module projects |
kover-report-path |
No | "build/reports/kover/report.xml" | Path to Kover XML report |
catalog-info-path |
No | "catalog-info.yaml" | Path to Backstage catalog file |
cloc-source-path |
No | "." | Path to analyze for lines of code |
cloc-exclude-dirs |
No | "build,target,dist,node_modules,.gradle,.idea,out" | Directories to exclude from LOC count |
test-timeout-minutes |
No | 30 | Test timeout in minutes |
| Secret | Required | Description |
|---|---|---|
TAILSCALE_AUTHKEY |
Yes | Tailscale authentication key |
GHL_USERNAME |
Yes | GitHub username for Gradle dependencies |
GHL_PASSWORD |
Yes | GitHub token for Gradle dependencies |
jobs:
code-coverage:
uses: monta-app/github-workflows/.github/workflows/code-coverage-kotlin.yml@main
with:
service-name: "my-kotlin-service"
java-version: "21"
secrets:
TAILSCALE_AUTHKEY: ${{ secrets.TAILSCALE_AUTHKEY }}
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}File: component-build.yml
Purpose: Builds an ARM64 Docker image and pushes it to Amazon ECR.
- Updates Slack with build progress
- Builds a single-platform ARM64 Docker image
- Pushes the image to ECR tagged with the commit SHA and
latest - Notifies Slack of build completion
| Input | Required | Default | Description |
|---|---|---|---|
stage |
Yes | - | Deployment stage: "dev", "staging", or "production" |
service-name |
Yes | - | Display name (e.g., "OCPP Service") |
service-emoji |
Yes | - | Emoji for Slack notifications |
service-identifier |
Yes | - | Service identifier (e.g., "ocpp", "vehicle") |
runner-size |
No | "normal" | Runner size: "normal" or "large" |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
region |
No | "eu-west-1" | AWS region |
docker-file-name |
No | "Dockerfile" | Dockerfile name |
additional-build-args |
No | - | Additional Docker build arguments |
ecr-repository-name |
No | - | Custom ECR repository name |
slack-message-id |
No | - | Existing Slack message ID to update |
| Secret | Required | Description |
|---|---|---|
AWS_ACCOUNT_ID |
Yes | AWS account ID |
SLACK_APP_TOKEN |
Yes | Slack app token |
GHL_USERNAME |
No | GitHub username (required for Kotlin) |
GHL_PASSWORD |
No | GitHub token (required for Kotlin) |
SENTRY_AUTH_TOKEN |
No | Sentry authentication token |
AWS_CDN_ACCESS_KEY_ID |
No | CDN access key ID |
AWS_CDN_SECRET_ACCESS_KEY |
No | CDN secret access key |
image-tag: The SHA tag of the built image
jobs:
build:
uses: monta-app/github-workflows/.github/workflows/component-build.yml@main
with:
stage: "staging"
service-name: "Vehicle Service"
service-emoji: "π"
service-identifier: "vehicle"
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}File: component-deploy.yml
Purpose: Deploys a service by updating Kubernetes manifests in the kube-manifests repository.
- Updates Slack with deployment progress
- Checks out the kube-manifests repository
- Updates image tag and metadata in values.yaml
- Updates deployment history in config.yaml
- Commits and pushes changes to trigger ArgoCD deployment
- Notifies Slack of deployment status
| Input | Required | Default | Description |
|---|---|---|---|
stage |
Yes | - | Deployment stage: "dev", "staging", or "production" |
service-name |
Yes | - | Display name (e.g., "OCPP Service") |
service-emoji |
Yes | - | Emoji for Slack notifications |
service-identifier |
Yes | - | Service identifier (e.g., "ocpp", "vehicle") |
image-tag |
Yes | - | Docker image tag to deploy |
slack-message-id |
No | - | Existing Slack message ID to update |
| Secret | Required | Description |
|---|---|---|
MANIFEST_REPO_PAT |
Yes | GitHub PAT for kube-manifests repo |
SLACK_APP_TOKEN |
Yes | Slack app token |
slack-message-id: Slack message ID for updates
jobs:
deploy:
uses: monta-app/github-workflows/.github/workflows/component-deploy.yml@main
with:
stage: "production"
service-name: "Payment Service"
service-emoji: "π³"
service-identifier: "payment"
image-tag: "abc123def456"
secrets:
MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}File: component-initialize.yml
Purpose: Initializes the CI/CD pipeline by creating a Slack notification message.
- Creates an initial Slack message for tracking pipeline progress
- Returns the message ID for subsequent workflow updates
| Input | Required | Default | Description |
|---|---|---|---|
service-name |
Yes | - | Display name (e.g., "OCPP Service") |
service-emoji |
Yes | - | Emoji for Slack notifications |
| Secret | Required | Description |
|---|---|---|
SLACK_APP_TOKEN |
Yes | Slack app token |
slack-message-id: Slack message ID for updatesslack-channel-id: Slack channel ID
jobs:
init:
uses: monta-app/github-workflows/.github/workflows/component-initialize.yml@main
with:
service-name: "User Service"
service-emoji: "π€"
secrets:
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}File: component-test-kotlin.yml
Purpose: Runs tests for Kotlin projects with Gradle.
- Sets up Java environment
- Runs Gradle tests
- Uploads test results as artifacts
- Updates Slack with test status
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | - | Runner size for the job |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
service-name |
No | - | Display name for Slack |
service-emoji |
No | - | Emoji for Slack |
gradle-module |
No | - | Gradle module name |
java-version |
No | "21" | Java version |
slack-message-id |
No | - | Slack message ID to update |
| Secret | Required | Description |
|---|---|---|
GHL_USERNAME |
Yes | GitHub username |
GHL_PASSWORD |
Yes | GitHub token |
SLACK_APP_TOKEN |
Yes | Slack app token |
jobs:
test:
uses: monta-app/github-workflows/.github/workflows/component-test-kotlin.yml@main
with:
java-version: "21"
service-name: "API Service"
service-emoji: "π"
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}File: component-test-python.yml
Purpose: Runs tests for Python projects using pytest with uv.
- Sets up Python environment
- Optionally starts Docker Compose services if
docker-compose-pathis provided - Waits for Docker containers to be healthy
- Creates .env file from
TEST_ENV_FILEsecret if provided - Installs uv for fast dependency management
- Runs
uv syncto install dependencies frompyproject.toml - Executes pytest with HTML and JUnit reports
- Uploads test results as artifacts
- Updates Slack with test status
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | - | Runner size for the job |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
service-name |
No | - | Display name for Slack |
service-emoji |
No | - | Emoji for Slack |
python-version |
No | "3.13" | Python version |
test-directory |
No | "tests" | Directory containing test files |
pytest-args |
No | "" | Additional pytest arguments |
docker-compose-path |
No | - | File path of the docker compose file |
slack-message-id |
No | - | Slack message ID to update |
| Secret | Required | Description |
|---|---|---|
SLACK_APP_TOKEN |
Yes | Slack app token |
TEST_ENV_FILE |
No | Environment variables for tests in .env format |
- Project must use
pyproject.tomlfor dependency management - Test dependencies should be included in dev dependencies
jobs:
test:
uses: monta-app/github-workflows/.github/workflows/component-test-python.yml@main
with:
python-version: "3.13"
test-directory: "tests"
service-name: "ML Service"
service-emoji: "π€"
secrets:
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
TEST_ENV_FILE: ${{ secrets.TEST_ENV_FILE }}File: deploy-kotlin.yml
Purpose: Complete CI/CD pipeline for Kotlin services including testing, building Docker images, and deploying to Kubernetes.
- Create Release Tag (optional): Creates a release tag when triggered via workflow_dispatch and
enable-release-tagis true - Initialize: Sets up Slack notifications for the deployment process
- Test: Runs unit tests and validates code quality
- Build: Builds an ARM64 Docker image and pushes to ECR
- Deploy: Updates Kubernetes manifests for deployment
- Create Changelog (optional): Generates and publishes changelog to Slack and GitHub releases
The workflow uses conditional execution to ensure proper handling of optional features:
- Create Release Tag: Only runs when
enable-release-tag: trueAND triggered viaworkflow_dispatch - Initialize: Always runs if release tag job succeeded or was skipped
- Test & Build: Run in parallel after Initialize succeeds, using
if: always()to handle skipped dependencies - Deploy: Only runs when ALL required jobs (Initialize, Test, Build) succeed
- Create Changelog: Only runs when
enable-changelog: trueAND Deploy succeeds
This conditional logic ensures the workflow continues properly even when optional features are disabled.
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | "normal" | Runner size: "normal" or "large" |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
stage |
Yes | - | Deployment stage: "dev", "staging", or "production" |
service-name |
Yes | - | Human-readable service name (e.g., "Charging Service") |
service-emoji |
Yes | - | Emoji to identify the service in Slack notifications |
service-identifier |
Yes | - | Service identifier for ECR and Kubernetes (e.g., "charging") |
gradle-module |
No | - | Gradle module name for multi-module projects |
java-version |
No | "21" | Java version to use |
gradle-args |
No | "--no-daemon --parallel" | Additional Gradle arguments |
region |
No | "eu-west-1" | AWS region for deployment |
docker-file-name |
No | "Dockerfile" | Name of the Dockerfile to build |
additional-build-args |
No | - | Additional Docker build arguments |
ecr-repository-name |
No | - | Override ECR repository name |
enable-release-tag |
No | false | Enable automatic release tag creation on workflow_dispatch |
release-tag-prefix |
No | '' | Optional prefix for release tag names. If provided, creates tags like 'prefix-YYYY-MM-DD-HH-MM' |
enable-changelog |
No | false | Enable changelog generation after deployment |
changelog-tag-pattern |
No | - | Regex pattern for matching tag patterns (group 1 should match version) - example: processor-(.*) |
changelog-path-exclude-pattern |
No | - | Regex pattern for excluding file paths from changelog - example: ^gateway/ |
| Secret | Required | Description |
|---|---|---|
GHL_USERNAME |
Yes | GitHub username for Gradle dependencies |
GHL_PASSWORD |
Yes | GitHub token for Gradle dependencies |
AWS_ACCOUNT_ID |
Yes | AWS Account ID for ECR and deployment |
SLACK_APP_TOKEN |
Yes | Slack token for notifications |
MANIFEST_REPO_PAT |
Yes | GitHub PAT for updating kube-manifests |
SENTRY_AUTH_TOKEN |
No | Sentry authentication token |
AWS_CDN_ACCESS_KEY_ID |
No | CDN access key for S3 access |
AWS_CDN_SECRET_ACCESS_KEY |
No | CDN secret key for S3 access |
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
uses: monta-app/github-workflows/.github/workflows/deploy-kotlin.yml@main
with:
stage: "production"
service-name: "Charging Service"
service-emoji: "β‘"
service-identifier: "charging"
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}name: Deploy Production with Release
on:
push:
tags: ['*']
workflow_dispatch:
jobs:
deploy:
uses: monta-app/github-workflows/.github/workflows/deploy-kotlin.yml@main
with:
stage: "production"
runner-size: "large"
service-name: "Charging Service"
service-emoji: "β‘"
service-identifier: "charging"
gradle-module: "charging-service"
enable-release-tag: true # Creates tag on manual trigger
enable-changelog: true # Generates changelog after deploy
# Optional: Prefix for release tags
release-tag-prefix: "charging" # Creates tags like 'charging-2024-01-15-14-30'
# Optional: Filter changelog by tag pattern (e.g., for monorepo)
changelog-tag-pattern: "charging-(.*)"
# Optional: Exclude paths from changelog (e.g., other services)
changelog-path-exclude-pattern: "^(payment|vehicle)/"
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}File: deploy-python.yml
Purpose: Complete CI/CD pipeline for Python services (test β build β deploy).
- Initializes Slack notification
- Runs Python tests using pytest and uv
- Builds an ARM64 Docker image
- Deploys to Kubernetes via manifest updates
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | "normal" | Runner size ("normal" or "large") |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
stage |
Yes | - | Deployment stage (dev/staging/production) |
service-name |
Yes | - | Service display name |
service-emoji |
Yes | - | Service emoji |
service-identifier |
Yes | - | Service identifier for K8s |
region |
No | "eu-west-1" | AWS region |
docker-file-name |
No | "Dockerfile" | Dockerfile name |
additional-build-args |
No | - | Extra Docker build args |
ecr-repository-name |
No | - | ECR repository override |
docker-compose-path |
No | - | File path of the docker compose file |
| Secret | Required | Description |
|---|---|---|
AWS_ACCOUNT_ID |
Yes | AWS account for ECR |
SLACK_APP_TOKEN |
Yes | Slack app token |
MANIFEST_REPO_PAT |
Yes | GitHub PAT for manifest repo |
SENTRY_AUTH_TOKEN |
No | Sentry auth token |
AWS_CDN_ACCESS_KEY_ID |
No | CDN access key |
AWS_CDN_SECRET_ACCESS_KEY |
No | CDN secret key |
TEST_ENV_FILE |
No | Environment variables for tests in .env format |
- Python project with
pyproject.toml - Tests in
tests/directory (configurable) - Dockerfile for containerization
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
uses: monta-app/github-workflows/.github/workflows/deploy-python.yml@main
with:
stage: "production"
service-name: "ML Service"
service-emoji: "π€"
service-identifier: "ml-service"
region: "eu-west-1"
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
TEST_ENV_FILE: ${{ secrets.TEST_ENV_FILE }}File: deploy-kotlin-v2.yml
Purpose: Complete CI/CD pipeline for Kotlin services using service repository-based deployment pattern. This workflow follows the v2 pattern where helm values are stored in a separate service repository instead of the central kube-manifests repo.
- Initialize: Sets up Slack notifications for the deployment process
- Test: Runs unit tests and validates code quality (optional, controlled by
run-tests) - Build: Builds an ARM64 Docker image and pushes to ECR
- Deploy: Updates helm values in the service repository and syncs with ArgoCD
- Update Service Profile (optional): Generates linkerd service profile from OpenAPI spec
- Create Release Tag (optional): Creates a release tag when
enable-release-tagis true - Create Changelog (optional): Generates and publishes changelog to Slack and GitHub releases
- Uses
component-deploy-v2.ymlwhich updates a separate service repository instead of kube-manifests - Adds
helm-values-pathinput for flexible helm values directory structure - Adds
repository-nameinput to specify the service repository name - Adds
argocd-app-nameinput for custom ArgoCD application naming
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | "normal" | Runner size: "normal" or "large" |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
stage |
Yes | - | Deployment stage: "dev", "staging", or "production" |
service-name |
Yes | - | Human-readable service name (e.g., "Charging Service") |
service-emoji |
Yes | - | Emoji to identify the service in Slack notifications |
service-identifier |
Yes | - | Service identifier for ECR and Kubernetes (e.g., "charging") |
gradle-module |
No | - | Gradle module name for multi-module projects |
java-version |
No | "21" | Java version to use |
gradle-args |
No | "--no-daemon --parallel" | Additional Gradle arguments |
region |
No | "eu-west-1" | AWS region for deployment |
docker-file-name |
No | "Dockerfile" | Name of the Dockerfile to build |
additional-build-args |
No | - | Additional Docker build arguments |
ecr-repository-name |
No | - | Override ECR repository name |
run-tests |
No | true | Run tests before deployment |
update-service-profile |
No | true | Generate linkerd service profile from OpenAPI spec |
helm-values-path |
No | helm/{service-identifier}/{stage}/app |
Path to helm values directory in service repo |
repository-name |
No | service-{service-identifier} |
Service repository name override |
argocd-server |
No | - | ArgoCD server URL (e.g., argocd.monta.app) |
argocd-app-name |
No | {service-identifier} |
ArgoCD application name prefix (without stage) |
argocd-sync-wait-seconds |
No | 300 | Timeout for ArgoCD sync monitoring |
git-sha |
No | - | Git SHA to checkout and build from |
enable-release-tag |
No | false | Enable automatic release tag creation |
release-tag-prefix |
No | '' | Optional prefix for release tag names |
enable-changelog |
No | false | Enable changelog generation |
changelog-tag-pattern |
No | - | Regex pattern for tag matching (monorepo filtering) |
changelog-path-exclude-pattern |
No | - | Regex pattern for excluding paths from changelog |
comment-on-prs |
No | true | Comment on PRs with deployment info |
comment-on-jira |
No | true | Comment on JIRA tickets with deployment info |
| Secret | Required | Description |
|---|---|---|
GHL_USERNAME |
Yes | GitHub username for Gradle dependencies |
GHL_PASSWORD |
Yes | GitHub token for Gradle dependencies |
AWS_ACCOUNT_ID |
Yes | AWS Account ID for ECR and deployment |
SLACK_APP_TOKEN |
Yes | Slack token for notifications |
MANIFEST_REPO_PAT |
Yes | GitHub PAT for updating service repository |
SENTRY_AUTH_TOKEN |
No | Sentry authentication token |
AWS_CDN_ACCESS_KEY_ID |
No | CDN access key for S3 access |
AWS_CDN_SECRET_ACCESS_KEY |
No | CDN secret key for S3 access |
LOKALISE_TOKEN |
No | Lokalise API token |
LOKALISE_PROJECT_ID |
No | Lokalise project ID |
JIRA_EMAIL |
No | Email for JIRA API authentication |
JIRA_TOKEN |
No | JIRA API token |
CHANGELOG_GITHUB_TOKEN |
No | GitHub token for changelog generation |
ARGOCD_TOKEN |
No | ArgoCD authentication token |
slack-message-id: Slack message ID for notificationsstage: Stage that was deployed todocker-image: Docker image repository pathprevious-image-tag: Previous Docker image tag (for rollback)image-tag: Deployed Docker image tagdeployment-start-time: ISO 8601 timestamp when deployment starteddeployment-end-time: ISO 8601 timestamp when deployment completeddeployment-url: Direct URL to the application in ArgoCD UI
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
uses: monta-app/github-workflows/.github/workflows/deploy-kotlin-v2.yml@main
with:
stage: "production"
service-name: "Payment Service"
service-emoji: "π³"
service-identifier: "payment"
repository-name: "service-payment" # Service repo with helm values
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}name: Deploy to Staging
on:
workflow_dispatch:
jobs:
deploy:
uses: monta-app/github-workflows/.github/workflows/deploy-kotlin-v2.yml@main
with:
stage: "staging"
service-name: "Auth Service"
service-emoji: "π"
service-identifier: "auth"
repository-name: "service-auth"
helm-values-path: "infrastructure/helm/staging/app"
argocd-server: "argocd.monta.app"
argocd-app-name: "auth"
java-version: "21"
gradle-module: "auth-api"
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }}name: Deploy Production with Release
on:
push:
tags: ['*']
workflow_dispatch:
jobs:
deploy:
uses: monta-app/github-workflows/.github/workflows/deploy-kotlin-v2.yml@main
with:
stage: "production"
runner-size: "large"
service-name: "Gateway Service"
service-emoji: "πͺ"
service-identifier: "gateway"
repository-name: "service-gateway"
enable-release-tag: true
release-tag-prefix: "gateway"
enable-changelog: true
changelog-tag-pattern: "gateway-(.*)"
argocd-server: "argocd.monta.app"
comment-on-prs: true
comment-on-jira: true
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
AWS_ACCOUNT_ID: ${{ secrets.PRODUCTION_AWS_ACCOUNT_ID }}
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}
MANIFEST_REPO_PAT: ${{ secrets.MANIFEST_REPO_PAT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }}
ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }}The service repository should have the following structure:
service-{identifier}/
βββ helm/
β βββ {service-identifier}/
β β βββ dev/
β β β βββ app/
β β β β βββ values.yaml
β β β βββ cluster/
β β β βββ config.yaml # Optional: ArgoCD auto-sync config
β β βββ staging/
β β β βββ app/
β β β βββ values.yaml
β β βββ production/
β β βββ app/
β β βββ values.yaml
βββ ...
Or with custom path:
service-{identifier}/
βββ infrastructure/
β βββ helm/
β βββ dev/
β β βββ app/
β β βββ values.yaml
β βββ staging/
β βββ app/
β βββ values.yaml
βββ ...
-
Use V2 (
deploy-kotlin-v2.yml) when:- Helm values should live in the service repository
- You want tighter coupling between code and infrastructure
- Each service manages its own deployment configuration
-
Use V1 (
deploy-kotlin.yml) when:- Helm values are centralized in the
kube-manifestsrepository - You prefer separation of concerns between code and infrastructure
- Infrastructure team manages all deployment configurations
- Helm values are centralized in the
File: publish-tech-docs.yml
Purpose: Publishes technical documentation to Backstage via AWS S3.
- Sets up Node.js and Python environments
- Installs techdocs-cli and mkdocs
- Generates documentation site
- Publishes to S3 bucket for Backstage
None - uses repository name as entity name
| Secret | Required | Description |
|---|---|---|
TECHDOCS_AWS_ACCESS_KEY_ID |
Yes | AWS access key for S3 |
TECHDOCS_AWS_SECRET_ACCESS_KEY |
Yes | AWS secret key for S3 |
jobs:
publish-docs:
uses: monta-app/github-workflows/.github/workflows/publish-tech-docs.yml@main
secrets:
TECHDOCS_AWS_ACCESS_KEY_ID: ${{ secrets.TECHDOCS_AWS_ACCESS_KEY_ID }}
TECHDOCS_AWS_SECRET_ACCESS_KEY: ${{ secrets.TECHDOCS_AWS_SECRET_ACCESS_KEY }}File: pull-request-kotlin.yml
Purpose: Validates Kotlin pull requests with linting, testing, and code coverage.
- Validates PR title format
- Runs Kotlin linter (ktlint)
- Executes tests with Kover coverage
- Uploads results to SonarCloud
- Comments coverage report on PR
- Publishes test results
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | "normal" | Runner size |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
java-version |
No | "21" | Java version |
gradle-module |
No | - | Gradle module name |
kover-report-path |
No | "build/reports/kover/report.xml" | Kover report path |
test-timeout-minutes |
No | 30 | Test timeout |
skip-sonar |
No | false | Skip SonarCloud analysis |
| Secret | Required | Description |
|---|---|---|
GHL_USERNAME |
Yes | GitHub username |
GHL_PASSWORD |
Yes | GitHub token |
SONAR_TOKEN |
Yes | SonarCloud token |
name: PR Validation
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
validate:
uses: monta-app/github-workflows/.github/workflows/pull-request-kotlin.yml@main
with:
java-version: "21"
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}File: pull-request-react-bun.yml
Purpose: Validates React/TypeScript pull requests using Bun runtime with code coverage reporting.
- Validates PR title format
- Starts optional Docker Compose services if
docker-compose-pathis provided - Waits for Docker containers to be healthy
- Installs dependencies with Bun
- Runs linter
- Runs tests (if configured)
- Builds the project
- Reports code coverage using LCOV format
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | "normal" | Runner size |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
bun-version |
No | "latest" | Bun version |
working-directory |
No | "." | Frontend code directory |
build-timeout-minutes |
No | 15 | Build timeout |
lint-command |
No | "bun run lint" | Lint command |
build-command |
No | "bun run build" | Build command |
test-command |
No | "bun run test" | Test command (optional) |
docker-compose-path |
No | - | Path to Docker Compose file for services |
name: PR Validation
on:
pull_request:
jobs:
validate:
uses: monta-app/github-workflows/.github/workflows/pull-request-bun.yml@main
with:
working-directory: "./frontend"
lint-command: "bun run lint:all"
docker-compose-path: "docker-compose.test.yml"The workflow automatically reports code coverage if your project generates LCOV coverage files in the coverage/ directory. The coverage report is posted as a comment on the pull request.
File: pull-request-react-pnpm.yml
Purpose: Validates React/TypeScript pull requests using pnpm package manager.
- Validates PR title format
- Sets up pnpm and Node.js
- Installs dependencies
- Runs linter
- Runs tests (if configured)
- Builds the project
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | "normal" | Runner size |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
node-version |
No | "lts/jod" | Node.js version |
pnpm-version |
No | "10" | pnpm version |
working-directory |
No | "." | Frontend code directory |
build-timeout-minutes |
No | 15 | Build timeout |
lint-command |
No | "pnpm run lint" | Lint command |
build-command |
No | "pnpm run build" | Build command |
test-command |
No | "pnpm run test" | Test command (optional) |
name: PR Validation
on:
pull_request:
jobs:
validate:
uses: monta-app/github-workflows/.github/workflows/pull-request-react.yml@main
with:
node-version: "20"
pnpm-version: "9"File: rollback.yml
Purpose: Rolls back a service deployment to a previous commit by updating Kubernetes manifests.
- Sends a Slack notification about the rollback (if not in dry-run mode)
- Identifies the commit to roll back to (defaults to the previous commit)
- Updates the Kubernetes manifests in the kube-manifests repository
- Updates the image tag and revision in values.yaml
- Updates deployment history in config.yaml
- Optionally blocks further deployments by calling the block-deploys workflow
| Input | Required | Default | Description |
|---|---|---|---|
commit-sha |
No | HEAD^ | Commit to roll back to (defaults to previous commit) |
service-name |
Yes | - | Proper name for your service (e.g., "OCPP Service") |
service-identifier |
Yes | - | Identifier of the service (e.g., "ocpp", "vehicle") |
slack-channel |
Yes | - | Slack channel for rollback notifications |
environment |
Yes | - | Deployment environment: "dev", "staging", or "production" |
dry-run |
No | false | Set to true to show rollback without pushing (disables Slack) |
block-workflow |
No | - | Name of workflow to block after rollback (e.g., "deploy-production.yml") |
| Secret | Required | Description |
|---|---|---|
SLACK_WEBHOOK |
Yes | Slack webhook URL for notifications |
ADMIN_PAT |
Yes | GitHub PAT for updating workflows and pushing to kube-manifests repo |
jobs:
rollback:
uses: monta-app/github-workflows/.github/workflows/rollback.yml@main
with:
service-name: "Charging Service"
service-identifier: "charging"
slack-channel: "#deploys"
environment: "production"
block-workflow: "deploy-production.yml"
secrets:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
ADMIN_PAT: ${{ secrets.PAT }}File: sonar-cloud.yml
Purpose: Runs dedicated SonarCloud analysis for code quality metrics.
- Checks out code with full history
- Sets up Java environment
- Caches SonarCloud packages
- Runs Kover coverage report
- Uploads analysis to SonarCloud
| Input | Required | Default | Description |
|---|---|---|---|
runner-size |
No | "normal" | Runner size |
use-blacksmith-runners |
No | true | Run on Blacksmith arm64 cloud runners (default). Set to false to run on self-hosted linux-arm64 |
java-version |
No | "21" | Java version |
gradle-module |
No | - | Gradle module name |
| Secret | Required | Description |
|---|---|---|
GHL_USERNAME |
Yes | GitHub username |
GHL_PASSWORD |
Yes | GitHub token |
SONAR_TOKEN |
Yes | SonarCloud token |
jobs:
sonarcloud:
uses: monta-app/github-workflows/.github/workflows/sonar-cloud.yml@main
with:
java-version: "21"
secrets:
GHL_USERNAME: ${{ secrets.GHL_USERNAME }}
GHL_PASSWORD: ${{ secrets.GHL_PASSWORD }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}File: track-pending-release.yml
Purpose: For repositories where production releases are triggered manually (via workflow dispatch or tags), this workflow maintains a persistent GitHub issue that shows at a glance what commits are pending deployment. The issue title uses color-coded emoji indicators to quickly show deployment status in your issue list.
- Compares latest production release tag with main branch
- Creates/updates a persistent GitHub issue with status emoji in title
- Shows commit list with links and deployment instructions
- Updates automatically on every push to main and after production deployments
- π’ Green: Production is up-to-date (0 commits)
- π‘ Yellow: 1 commit pending
- π΄ Red: Multiple commits pending
| Input | Required | Default | Description |
|---|---|---|---|
main-branch |
No | "main" | Main branch name to track |
production-workflow |
No | "deploy-production.yml" | Production deployment workflow filename |
None required - uses GITHUB_TOKEN which is automatically provided by GitHub Actions.
- Service must use
enable-release-tag: truein production deployment workflow - Production deployment must create git tags (used to identify deployed version)
name: Track Pending Release
on:
push:
branches:
- main
tags:
- '*'
workflow_dispatch:
workflow_run:
workflows: ["Deploy Production"] # Must match your production workflow name
types:
- completed
jobs:
track-release:
uses: monta-app/github-workflows/.github/workflows/track-pending-release.yml@main
with:
main-branch: 'main'
production-workflow: 'deploy-production.yml'Important: The workflow_run trigger is required because enable-release-tag creates tags using GITHUB_TOKEN, which doesn't trigger other workflows. This ensures the tracker updates immediately after production deploys.
jobs:
track-release:
uses: monta-app/github-workflows/.github/workflows/track-pending-release.yml@main
with:
main-branch: 'master'
production-workflow: 'deploy-prod.yml'- Title:
π’ Production Release Tracker(changes to π‘ or π΄ based on pending commits) - Body: Shows latest deploy tag, current main commit, and list of pending commits with links
- When up-to-date: Displays "Recently Released" confirmation message
- When pending: Shows commit list with direct link to trigger deployment via workflow dispatch
- Service must use
enable-release-tag: truein production deployment workflow (creates the tags this workflow tracks)
- Pin the issue for easy team access
- Include tag trigger (
tags: ['*']) to update immediately after production deploys - Pairs well with
enable-changelog: truefor automated release notes
Most workflows require organization or repository secrets. Add these in your repository settings:
-
GitHub Tokens:
GHL_USERNAME: Your GitHub usernameGHL_PASSWORD: A GitHub personal access token withread:packagesscopeMANIFEST_REPO_PAT: PAT with write access to kube-manifests repo
-
AWS Credentials:
AWS_ACCOUNT_ID: Your AWS account IDTECHDOCS_AWS_ACCESS_KEY_ID: AWS access key for TechDocs S3TECHDOCS_AWS_SECRET_ACCESS_KEY: AWS secret key for TechDocs S3
-
Third-party Services:
SLACK_APP_TOKEN: Slack app-level tokenSONAR_TOKEN: SonarCloud authentication tokenTAILSCALE_AUTHKEY: Tailscale VPN authentication keySENTRY_AUTH_TOKEN: Sentry authentication token (optional)
-
Kotlin Projects:
- Must have Gradle build configuration
- Should include Kover plugin for coverage
- SonarCloud configuration in
build.gradle.kts
-
React Projects:
- Package.json with lint, build, and test scripts
- TypeScript configuration
- Either Bun or pnpm as package manager
-
Kubernetes Deployments:
- Manifests must be in
kube-manifestsrepository - Structure:
apps/<service-identifier>/<stage>/app/values.yaml - Config file:
apps/<service-identifier>/<stage>/cluster/config.yaml
- Manifests must be in
- Version Pinning: Always use a specific version tag (e.g.,
@main) when calling workflows - Runner Sizing: Use "large" runners for resource-intensive builds
- Timeouts: Adjust timeouts based on your project's build/test duration
- Slack Integration: Use consistent service names and emojis across workflows
- Multi-Module Projects: Specify
gradle-modulefor targeted builds - Environment Stages: Use consistent stage names: "dev", "staging", "production"
Common Issues:
-
Gradle Build Failures:
- Ensure
GHL_USERNAMEandGHL_PASSWORDsecrets are set - Check that GitHub packages are properly configured
- Ensure
-
Docker Build Failures:
- Verify Dockerfile exists at specified path
- Check build arguments syntax
- Ensure secrets are passed correctly
-
Deployment Failures:
- Verify manifest repository structure
- Check PAT permissions for kube-manifests
- Ensure service identifier matches manifest paths
-
Coverage Report Issues:
- Verify Kover plugin is configured
- Check report path matches actual output
- Ensure tests generate coverage data
For additional support, check the workflow logs in GitHub Actions or contact the platform team.
File: pr-digest.yml
Purpose: Posts a daily Slack digest of open pull requests in the calling repository. Designed for high-volume repos (100+ open PRs): the message is AI-curated by default so the channel sees a focused triage view ("today's focus", critical PRs, area summary) instead of a flat list of every open PR. Falls back to deterministic oldest-first surfacing when AI is disabled or unavailable.
- Fetches all open PRs in the calling repo via the GitHub CLI (no checkout needed).
- Filters out excluded authors (default: Dependabot, Renovate) and drafts (configurable).
- Buckets PRs as
fresh,stale(β₯stale-threshold-days), orcritical(β₯critical-threshold-days). - Optional: sends a compact JSON of the data to the Anthropic API (Claude) and asks it to produce a 1β2 sentence headline plus up to N focus PRs with one-line "why this matters" notes.
- Builds a Slack Block Kit message with: header + counts, AI headline (if any), today's focus PRs, area summary, deep-link footer.
- Optional: posts a threaded follow-up with the full per-area PR breakdown.
- Posts via
chat.postMessageusing the existingSLACK_APP_TOKEN.
| Input | Required | Default | Description |
|---|---|---|---|
slack-channel-id |
Yes | - | Slack channel ID (e.g. C0ALEGFRPU1). The bot behind SLACK_APP_TOKEN must be a member. |
stale-threshold-days |
No | 2 |
Days of inactivity before a PR counts as "stale" in the headline stats. |
critical-threshold-days |
No | 7 |
Days of inactivity for a PR to be surfaced individually as "critical" (about to be auto-closed). |
max-prs-to-surface |
No | 5 |
Max PRs to render by name in the "Today's focus" section. |
include-drafts |
No | false |
Include draft PRs in the digest. |
exclude-authors |
No | "dependabot[bot],renovate[bot]" |
Comma-separated GitHub logins to exclude. |
use-ai-curation |
No | true |
Use Claude to pick focus PRs and write the headline. Falls back gracefully if the call fails. |
model |
No | "claude-sonnet-4-6" |
Anthropic model identifier (model list). Use claude-haiku-4-5 for ~10Γ cheaper, slightly less nuanced output. |
post-thread-breakdown |
No | true |
Reply in-thread with the full per-area PR breakdown. |
title |
No | repo name | Header text for the Slack message. |
empty-state |
No | "silent" |
When no PRs are stale: "silent" (post nothing) or "celebrate" (post a π message). |
| Secret | Required | Description |
|---|---|---|
SLACK_APP_TOKEN |
Yes | Slack bot token with chat:write scope. Bot must be in the target channel. |
ANTHROPIC_API_KEY |
If use-ai-curation: true |
Anthropic API key. If missing while AI curation is enabled, the AI step is skipped and the deterministic message ships. |
The workflow declares its own pull-requests: read and contents: read. The caller does not need to pass any extra permissions.
- Volume-aware: deterministic mode hard-caps focus to
max-prs-to-surface; the rest is summarised by area. The threaded breakdown carries the long tail so the main channel stays scannable even at 100+ open PRs. - Non-blocking AI: the Anthropic call has a 2-minute step timeout and
continue-on-error: true. If it fails, the digest ships without the AI headline / focus, falling back to oldest-first. - Area detection: PRs are auto-grouped by the first label matching the canonical area set (
hub,api-gateway,control-v2,portals,installers,control,ocpp-toolkit,storybook,dependencies,security). Anything else falls intoother. - Cost (Sonnet, ~100 PRs): roughly ~$0.04 per run, ~$1/month for weekday-only scheduling.
- Scheduling: GitHub Actions cron runs in UTC. For 9 AM Europe/Copenhagen year-round, use dual cron entries plus a small timezone guard in the caller (see the monorepo-typescript
automation-pr-digest.ymlfor the canonical pattern).
name: PR digest
on:
schedule:
- cron: "7 7 * * 1-5" # 09:07 CEST (summer)
- cron: "7 8 * * 1-5" # 09:07 CET (winter)
workflow_dispatch:
jobs:
schedule-guard:
runs-on: ubuntu-latest
outputs:
proceed: ${{ steps.check.outputs.proceed }}
steps:
- id: check
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"; exit 0
fi
LOCAL_HOUR=$(TZ=Europe/Copenhagen date +%H)
if [[ "$LOCAL_HOUR" == "09" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
echo "proceed=false (local hour=$LOCAL_HOUR)"
echo "proceed=false" >> "$GITHUB_OUTPUT"
fi
digest:
needs: schedule-guard
if: needs.schedule-guard.outputs.proceed == 'true'
uses: monta-app/github-workflows/.github/workflows/pr-digest.yml@main
with:
slack-channel-id: C0ALEGFRPU1
stale-threshold-days: 2
critical-threshold-days: 7
title: "my-repo β open PRs"
secrets: inheritjobs:
digest:
uses: monta-app/github-workflows/.github/workflows/pr-digest.yml@main
with:
slack-channel-id: C0XXXXXXXXX
use-ai-curation: false
post-thread-breakdown: false
empty-state: celebrate
secrets:
SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }}This reusable workflow only declares workflow_call β it cannot be dispatched directly. Manual testing is done via the caller workflow in the consuming repository, which must declare workflow_dispatch itself (both example callers above do).
Once the caller is in place, trigger from the GitHub UI ("Actions" tab β caller workflow β "Run workflow") or via the CLI:
gh workflow run <caller-workflow-filename>.yml --repo monta-app/<your-repo>The first manual run is the recommended way to verify channel membership, secrets, and AI model availability before relying on the schedule.