# Starter CD pipeline for the tasks-app — GitHub Actions flavor, extending the Module 14 CI file. # # The whole idea: CD is not a new system. It is MORE STAGES on the SAME pipeline, after the checks # pass. The lint/test gates below are the Module 14 pipeline, unchanged. Everything from the # `build-and-publish` job down is new in this module. # # Where this file goes: .github/workflows/cd.yml (or fold it into your existing ci.yml). On GitLab, # the same shape is stages in .gitlab-ci.yml with `needs:`/`rules:`; Forgejo/Gitea use Actions- # compatible YAML. The concept — gated stages from merge to running — is identical everywhere. # # VERIFY BEFORE PUBLISH: action versions, the registry login/build-push action names, and the # manual-approval mechanism all drift. Check current forge docs at build time (see README checklist). name: CD on: push: branches: [main] # only a MERGE to main triggers a deploy pull_request: # PRs still run the gates, but never deploy jobs: # ---- The Module 14 gates: nothing ships without passing these first. ---------------------------- check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install pytest ruff - run: ruff check . # lint - run: pytest -q # test # In a real pipeline a security-scan job (Module 15) would also gate here. # ---- Build the artifact ONCE and publish it. The unit of deploy is an immutable, SHA-tagged image. build-and-publish: needs: check # only runs if the gates passed if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # Log in to your container registry (Module 16's images need a durable home, like a Git remote # is for commits). Registry/credentials are provider-specific — supply them as secrets, # never inline (Module 17). # - uses: docker/login-action@v3 # with: # registry: ${{ vars.REGISTRY }} # username: ${{ secrets.REGISTRY_USER }} # password: ${{ secrets.REGISTRY_TOKEN }} # Build and push, tagging with the commit SHA (immutable + traceable) and :staging (moving). # - uses: docker/build-push-action@v6 # with: # push: true # tags: | # ${{ vars.REGISTRY }}/tasks-app:${{ github.sha }} # ${{ vars.REGISTRY }}/tasks-app:staging - run: echo "build + push tasks-app:${{ github.sha }} (wire up the registry steps above)" # ---- Deploy to a NON-prod environment automatically. Safe to do on every merge. ---------------- deploy-staging: needs: build-and-publish if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: # The five deploy steps live in deploy.sh in this folder. On a real target this would run the # platform's deploy (kubectl / platform CLI / compose) against the SHA-tagged image, inject # runtime config + secrets (Module 17), health-check, and roll back on failure. - run: echo "deploy tasks-app:${{ github.sha }} to STAGING, health-check, roll back if red" # ---- THIS JOB IS THE DELIVERY-vs-DEPLOYMENT SWITCH. --------------------------------------------- # # As written, `environment: production` requires a human to approve before this job runs (set a # required reviewer on the 'production' environment in the forge). That is CONTINUOUS DELIVERY: # the artifact is auto-built and staged; a person clicks to ship to prod. # # Delete the `environment:` block and this becomes CONTINUOUS DEPLOYMENT: merge -> prod, no human. # Only remove it once you trust your review + CI + security gates (Modules 10/14/15) more than you # trust the click. On GitLab the equivalent switch is `when: manual` vs. automatic. deploy-prod: needs: deploy-staging if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: production # <-- required-reviewer gate = delivery. Remove = deployment. steps: - run: echo "deploy tasks-app:${{ github.sha }} to PRODUCTION (gated on human approval)"