100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
CI/CD with GitHub Actions
50 minintermediate

Environments Practice — Gated Release

What You'll Build

You will configure a complete three-environment deployment pipeline for CricketPulse: dev (automatic), staging (single reviewer), and production (two reviewers, main branch only, 2-minute wait timer). You will create all three environments in GitHub Settings, configure their protection rules, and write a deployment workflow that flows through all three stages. The exercise includes deliberately approving and rejecting a deployment to experience the reviewer interface, and observing how the deployment history audit trail populates as each stage completes. By the end you will have a production-grade deployment governance system that mirrors the practices at organisations deploying to live user traffic.

Analogy🏏Cricket
🏏 Think of it like cricket: This exercise is building the complete match management infrastructure: the pitch is prepared (dev), the warm-up match is played (staging approval), and only after the selectors sign off does the Test match begin (production). You'll experience being both the deployer (pushing the commit that initiates the pipeline) and the approver (clicking the approval button in GitHub's reviewer interface) — understanding both sides of the gate is essential for designing gates that are genuinely useful rather than bureaucratic obstacles.

Prerequisites

  • The CricketPulse repository from previous exercises with an existing `.github/workflows/` directory.
  • GitHub account with admin access to the repository — environment creation requires repository admin or owner permissions.
  • Optionally: a second GitHub account or a colleague's GitHub account to act as the production approver (you can also approve your own deployments if you configure yourself as the required reviewer).
  • Understanding of environment secrets and `environment:` key in workflow YAML from Lessons 13–14.
  • The container image from Lesson 12 in `ghcr.io` (optional — the deploy step can be a simulated `echo` if the image isn't available).

Setup & Project Structure

Create the three environments in GitHub Settings and configure their protection rules before writing the workflow. Environment configuration is entirely in the GitHub UI — there is no YAML for environment protection rules. The workflow YAML simply references the environment by name, and GitHub enforces whatever rules are configured for that environment.

Analogy🏏Cricket
🏏 Think of it like cricket: configuring the three environments and their protection rules in the GitHub UI before writing any workflow is like agreeing the match's playing conditions with the officials before the toss — not mid-innings. Just as environment protection rules live entirely in settings and there is no YAML for them, the tournament's rules on reviews, powerplays, and appeals are fixed by the governing body off the field, not written into a batsman's technique. Just as the workflow simply names an environment and GitHub enforces whatever rules are configured, a player simply takes the field and the umpires enforce the pre-agreed conditions — the player doesn't carry the rulebook, the officials do. The payoff: settle the playing conditions once, in the right place, and every delivery afterwards is automatically governed by them without cluttering the play itself.
bash
# GitHub Settings configuration steps (done in the browser, not in YAML)

# 1. Navigate to: github.com/YOUR_USERNAME/cricketpulse/settings/environments

# 2. Create 'dev' environment:
#    Click 'New environment' → Name: dev
#    Protection rules: (leave all unchecked — automatic)
#    Variables → Add variable: DEPLOY_URL = https://dev.cricketpulse.example.com
#    Click 'Save protection rules'

# 3. Create 'staging' environment:
#    Click 'New environment' → Name: staging
#    Required reviewers: Add yourself (or a colleague)
#    Deployment branches: Select 'Selected branches' → Add rule: main
#    Variables → Add variable: DEPLOY_URL = https://staging.cricketpulse.example.com
#    Wait timer: 0 minutes
#    Click 'Save protection rules'

# 4. Create 'production' environment:
#    Click 'New environment' → Name: production
#    Required reviewers: Add yourself + one other (or just yourself twice won't work — add 2 different accounts)
#    Deployment branches: Select 'Selected branches' → Add rule: main
#    Wait timer: 2 minutes
#    Variables → Add variable: DEPLOY_URL = https://cricketpulse.example.com
#    Click 'Save protection rules'

# Verify environments are created:
# Settings → Environments should show: dev, staging, production
echo 'Environments created in GitHub Settings — proceed to workflow creation'

Step 1 — Foundation

Create the workflow file with the build job and the dev deployment job. Push to main and observe the workflow trigger — the dev deployment should run automatically without any approval gate, confirming the environment is correctly configured with no protection rules.

Analogy🏏Cricket
🏏 Think of it like cricket: The warm-up session runs on the hotel practice ground with no spectators, no formal protocols — players take the field and start immediately. The dev environment is the hotel practice ground: automated, immediate, no gatekeeping. Confirming the dev pipeline runs automatically before adding gated environments is like confirming the practice session proceeds smoothly before worrying about the match-day protocols.
yaml
# File: .github/workflows/cricketpulse-release.yml — Step 1

name: CricketPulse Release Pipeline

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      deploy-to-production:
        description: 'Deploy through to production after staging?'
        required: false
        type: boolean
        default: false
      reason:
        description: 'Deployment reason'
        required: false
        type: string

concurrency:
  group: release-${{ github.sha }}
  cancel-in-progress: false

jobs:
  build:
    name: Build CricketPulse
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.ver.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci && npm test
      - id: ver
        run: echo "version=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
      - uses: actions/upload-artifact@v4
        with:
          name: cricketpulse-${{ github.sha }}
          path: dist/
          if-no-files-found: warn

  deploy-dev:
    name: Deploy to Dev
    runs-on: ubuntu-latest
    needs: build
    environment: dev                  # No protection rules — runs immediately
    steps:
      - name: Deploy to dev environment
        run: |
          echo "=== CricketPulse Dev Deployment ==="
          echo "Version:  ${{ needs.build.outputs.version }}"
          echo "URL:      ${{ vars.DEPLOY_URL }}"
          echo "Deployer: ${{ github.actor }}"
          echo "SHA:      ${{ github.sha }}"
          echo "Status:   Deployed successfully (simulated)"

Step 2 — Core Logic

Add the staging deployment job that depends on dev and requires approval. Push to main and observe the workflow pause after dev deployment completes, entering the `waiting` state for staging. Use the GitHub UI to approve the staging deployment and observe the job resume. This approval experience is the core of the exercise.

Analogy🏏Cricket
🏏 Think of it like cricket: The warm-up session is complete (dev deployed). Now the match-day protocols begin: the umpires inspect the pitch, the team captains arrive for the toss, the grounds team prepare the outfield. The staging approval is the toss ceremony — a formal, structured pause before the match begins, requiring the participating parties to confirm readiness before play commences.
yaml
# Step 2: Add staging job to the workflow (append to jobs section)

  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: [build, deploy-dev]
    environment: staging              # Requires reviewer approval
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: cricketpulse-${{ github.sha }}
          path: dist/
        continue-on-error: true       # dist/ may not exist for non-build projects

      - name: Deploy to staging environment
        run: |
          echo "=== CricketPulse Staging Deployment ==="
          echo "Version:   ${{ needs.build.outputs.version }}"
          echo "URL:       ${{ vars.DEPLOY_URL }}"
          echo "Deployer:  ${{ github.actor }}"
          echo "Approved by: $(echo '${{ github.actor }}') (reviewer)"
          echo "Status:    Deployed to staging successfully (simulated)"

      - name: Run smoke tests against staging
        run: |
          echo "Smoke test: GET ${{ vars.DEPLOY_URL }}/health → 200 OK (simulated)"
          echo "Smoke test: GET ${{ vars.DEPLOY_URL }}/matches → 200 OK (simulated)"
          echo "All smoke tests passed"

Step 3 — Integration & Enhancement

Add the production deployment job with the 2-minute wait timer. After the staging deployment completes, the production gate activates — requiring approval, then waiting 2 minutes before executing. Observe the full pipeline timeline: build → dev (auto) → staging (approval wait) → production (approval + 2-min timer). The complete pipeline demonstrates the escalating protection levels as deployments approach production.

Analogy🏏Cricket
🏏 Think of it like cricket: After the warm-up (dev) and the pitch inspection ceremony (staging), the Test match finally begins with all the formal protocols: national anthems, player presentations, the coin toss, a final equipment check. Each is a structured delay ensuring everything is ready before the ball is bowled. The 2-minute production wait timer is the Test match's opening ceremony — a deliberate pause giving everyone a final opportunity to abort if something has gone wrong.
yaml
# Step 3: Add production job and final summary (complete workflow)

  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: [build, deploy-staging]
    # Only deploy to production if manually dispatched with the flag
    if: |
      github.event_name == 'workflow_dispatch' &&
      inputs.deploy-to-production == true
    environment: production           # 2 required reviewers + 2-min wait timer
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: cricketpulse-${{ github.sha }}
          path: dist/
        continue-on-error: true

      - name: Validate production deployment reason
        run: |
          REASON="${{ inputs.reason }}"
          if [[ -z "$REASON" || ${#REASON} -lt 5 ]]; then
            echo "ERROR: A meaningful deployment reason is required for production"
            exit 1
          fi

      - name: Deploy to production
        run: |
          echo "=== CricketPulse Production Deployment ==="
          echo "Version:    ${{ needs.build.outputs.version }}"
          echo "URL:        ${{ vars.DEPLOY_URL }}"
          echo "Deployer:   ${{ github.actor }}"
          echo "SHA:        ${{ github.sha }}"
          echo "Reason:     ${{ inputs.reason }}"
          echo "Status:     Deployed to production successfully (simulated)"

  deployment-summary:
    name: Deployment Summary
    runs-on: ubuntu-latest
    needs: [deploy-dev, deploy-staging, deploy-production]
    if: always()
    steps:
      - name: Write deployment summary
        run: |
          cat >> $GITHUB_STEP_SUMMARY << 'SUMMARY'
          ## 🏏 CricketPulse Release Summary
          | Environment | Status |
          |-------------|--------|
          | Dev         | ${{ needs.deploy-dev.result }} |
          | Staging     | ${{ needs.deploy-staging.result }} |
          | Production  | ${{ needs.deploy-production.result }} |

          **Version:** ${{ github.sha }}
          **Triggered by:** ${{ github.actor }}
          SUMMARY

Step 4 — Testing & Verification

Test three scenarios: automatic push (dev + staging gate, no production), manual dispatch with production enabled (full three-stage pipeline), and a deliberately rejected staging deployment (observe the rejection flow and how the pipeline stops).

Analogy🏏Cricket
🏏 Think of it like cricket: running the three scenarios — automatic push, manual dispatch with production, and a rejected staging deploy — is like rehearsing how a match unfolds under different calls before the real fixture. Just as an automatic push flows through dev and staging but stops short of production, an ordinary passage of play proceeds freely up to the boundary rope but can't cross into a scoring appeal without review. Just as manual dispatch with production enabled runs the full three-stage pipeline, a captain formally opting to declare unlocks the complete sequence of events that follows. And just as a deliberately rejected staging deployment halts the pipeline, a third umpire ruling not-out on review sends the batsman back and the passage of play simply stops there. Testing all three teaches you how each gate behaves. The payoff: like rehearsing every umpiring outcome in advance, you learn exactly how approvals let a deploy through, how manual triggers open the full path, and how a rejection cleanly stops the release.
bash
# Test scenarios

# SCENARIO 1: Automatic push — dev auto-deploys, staging waits for approval
git add .github/workflows/cricketpulse-release.yml
git commit -m 'feat: add three-stage gated release pipeline'
git push origin main

# Expected flow:
# build ✅ → deploy-dev ✅ (automatic) → deploy-staging ⏸️ (waiting for approval)
# Go to: github.com/YOUR_USERNAME/cricketpulse/actions
# Click the running workflow → click 'Review pending deployments'
# Approve staging → observe deploy-staging resume and complete
# deploy-production = skipped (push event, not workflow_dispatch)

# SCENARIO 2: Manual dispatch to production
# Go to: Actions → CricketPulse Release Pipeline → Run workflow
# Inputs: deploy-to-production = true, reason = 'Add live score feature for IPL 2024'
# Watch full three-stage pipeline run:
# build ✅ → dev ✅ → staging ⏸️ (approve) → production ⏸️ (approve + 2-min wait)

# SCENARIO 3: Reject staging deployment
# Trigger another push, then click 'Review pending deployments' on staging
# Click 'Reject' with a comment
# Expected: deploy-staging ❌ → deploy-production skipped → summary shows staging failed

# VERIFY DEPLOYMENT HISTORY:
# github.com/YOUR_USERNAME/cricketpulse/deployments
# Should show all environments with their deployment records

Warning: If you configure yourself as the sole required reviewer, GitHub may still allow you to approve your own deployments — but this defeats the purpose of the approval gate. For meaningful two-person integrity, ensure the required reviewer is a different GitHub account. If a second account isn't available for the exercise, accept this limitation and note it: in a real project, required reviewers must be different from the person who triggered the deployment.

Extension Challenge: Add a Slack notification step to the `deployment-summary` job that posts the deployment summary table to a Slack channel using a webhook URL stored as a repository secret. The notification should include clickable links to the workflow run URL (`${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`). This mirrors the deployment notification pattern used by teams to keep stakeholders informed of release status without requiring them to monitor the GitHub Actions UI.

  • Environments are created and configured entirely in GitHub Settings — protection rules (reviewers, branch restrictions, wait timer) are UI configuration, not YAML configuration.
  • The reviewer approval UI in GitHub Actions shows a 'Review pending deployments' button on the in-progress workflow run — approvers click this to see pending gates and approve or reject them.
  • A rejected deployment gate stops all subsequent jobs in the pipeline — the deployment record shows 'failure' for the rejected environment and all downstream jobs are skipped.
  • The deployment history at `github.com/OWNER/REPO/deployments` provides a complete, immutable audit trail of every deployment to every environment including approver identity and timestamps.
  • The 2-minute wait timer starts after the required approvals are collected — it is visible as a countdown in the job's pending state, giving approvers a window to abort before execution begins.
  • Combining `workflow_dispatch` with `inputs.deploy-to-production == true` as a gate means the production stage only runs on explicit manual dispatch with that flag set — pushing to main never reaches production automatically.
Lesson 16 of 24
0% complete