# Template: Release to PRODUCTION (manual, button-triggered)# Copy to: .github/workflows/tag-deploy.yml## Trigger: workflow_dispatch (Actions tab -> "Run workflow") with an explicit version.# Flow:# validate version (vX.Y.Z, not already tagged)# -> deploy to PROD (gated by the 'prod' GitHub Environment required reviewers)# -> create git tag vX.Y.Z + GitHub Release (only AFTER a successful, approved deploy)## Single approval gate: the reusable deploy job binds `environment: prod`, so the deploy# pauses for manual approval. The tag/Release is created only after that succeeds — no# orphan tags for rejected releases.## NOTE: the tag is pushed by GITHUB_TOKEN. The tag-protection ruleset must list the# GitHub Actions app in its bypass actors (see .github/ruleset/ruleset-tags.json), otherwise# tag creation is blocked once the ruleset is enforced (active).## Required secrets: Same as develop-deploy.yml
name: Release to Production
on: workflow_dispatch: inputs: version: description: 'Release version to cut and deploy (format vX.Y.Z, e.g. v1.4.0)' required: true type: string
# Deploys on one ref run one at a time, in order. Never cancel one mid-flight:# a half-applied deploy is worse than a late one.# A promotion waiting at the prod approval gate is replaced if a later# version is dispatched behind it; re-dispatch it if it was still wanted.concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false
permissions: contents: write # create tag + GitHub Release id-token: write
jobs: validate: name: Validate version runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Check version format and uniqueness env: VERSION: ${{ inputs.version }} REF: ${{ github.ref_name }} run: | if [ "$REF" != "main" ]; then echo "::error::Release must be dispatched from 'main' (got '$REF')."; exit 1 fi if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::version must match vX.Y.Z (got '$VERSION')"; exit 1 fi if git rev-parse "refs/tags/${VERSION}" >/dev/null 2>&1; then echo "::error::tag ${VERSION} already exists (releases are immutable)"; exit 1 fi echo "Version ${VERSION} is valid and unused."
deploy: name: Deploy to prod needs: validate uses: <org>/ci-templates/.github/workflows/java-main-pipeline.yml@v2 with: run_build: true run_test: false run_artifact: true image_tag: ${{ inputs.version }} # image tagged with the release version run_deploy: true deploy_target: 'ec2-vpn' environment: 'prod' # <- Environment required reviewers gate the deploy secrets: inherit
tag: name: Tag + GitHub Release needs: deploy runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Create tag and release env: VERSION: ${{ inputs.version }} GH_TOKEN: ${{ github.token }} run: | if ! git rev-parse "refs/tags/${VERSION}" >/dev/null 2>&1; then git tag "${VERSION}" git push origin "${VERSION}" echo "::notice::Created tag ${VERSION}." fi gh release view "${VERSION}" >/dev/null 2>&1 \ || gh release create "${VERSION}" --title "${VERSION}" --generate-notesJava (Spring Boot)·Consumer template·on: workflow_dispatch
Java Tag Deploy
Template: Release to PRODUCTION (manual, button-triggered) Copy to: .github/workflows/tag-deploy.yml Trigger: workflow_dispatch (Actions tab -> "Run workflow") with an explicit version. Flow: validate
templates/java-tag-deploy.yml