Course outline · 0% complete

0/29 lessons0%

Course overview →

Capstone part 2: the pipeline

lesson 10-2 · ~13 min · 29/29

The workflow, top half

One file, .github/workflows/ship.yml, and notesy tests, builds, ships, and deploys itself on every merge. First the CI half, straight from unit 7:

name: ship
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: pytest
ChoiceLesson
triggers on pushes to main and on pull requests7-2
each step gates the next through exit codes7-1
cheap steps before expensive ones7-3

Running on pull requests as well as main is what catches broken code before a merge rather than after. The bottom half adds the shipping stages.

The workflow, bottom half

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - run: docker login ghcr.io -u ada -p "${{ secrets.REGISTRY_TOKEN }}"
      - run: docker build -t ghcr.io/ada/notesy:${{ github.sha }} .
      - run: docker push ghcr.io/ada/notesy:${{ github.sha }}
      - run: ./scripts/deploy.sh ${{ github.sha }}
LineRoleLesson
needs: testwaits for the test job to pass8-1
if: github.ref == ...pull requests get tested, only main deploys7-2
environment: productionenvironment-scoped variables8-2
secrets.REGISTRY_TOKENthe encrypted registry credential8-2
tagging with the commit shatraceable images7-3
pushing to GHCRthe CI-to-server hand-off6-1

deploy.sh connects to the lesson 9-1 VM and runs docker compose pull && docker compose up -d.

The rollback plan is already tested and ready: rerun deploy.sh with the previous sha, exactly as lesson 8-3 described.

checkout test on main build image, sha tag push to GHCR deploy.sh sha a pull request stops here red ✗, nothing ships
The notesy pipeline end to end. Pull requests stop after the test job, and only a green commit on main continues through build, push, and deploy.

The whole pipeline as a shell simulation

A step function reporting a stage and its outcome, where [ "$2" = "ok" ] makes the function's exit code match its report.

step() {
  echo "$1: $2"
  [ "$2" = "ok" ]
}
(
  set -e
  step "checkout" "ok"
  step "test" "fail"
  step "build image" "ok"
)
if [ $? -eq 0 ]; then
  echo "pipeline succeeded"
else
  echo "pipeline failed, deploy skipped"
fi

Output

checkout: ok
test: fail
pipeline failed, deploy skipped
StageReportedRan
checkoutokyes
testfailyes, and aborted the run
build imagenever reachedno

The test stage fails, so set -e kills the run before anything ships. That is the entire value of the arrangement, since a failing test cannot produce a deployed image.

The five-stage pipeline, all green

With the tests fixed, every stage reports ok and the pipeline extends to five stages in order: checkout, test, build image, push image, and deploy.

step() {
  echo "$1: $2"
  [ "$2" = "ok" ]
}
(
  set -e
  step "checkout" "ok"
  step "test" "ok"
  step "build image" "ok"
  step "push image" "ok"
  step "deploy" "ok"
)
if [ $? -eq 0 ]; then
  echo "pipeline succeeded"
else
  echo "pipeline failed, deploy skipped"
fi

Output

checkout: ok
test: ok
build image: ok
push image: ok
deploy: ok
pipeline succeeded
StageReal-world command
checkoutactions/checkout@v4
testpytest
build imagedocker build
push imagedocker push
deploy./scripts/deploy.sh

All five stages stay inside the ( set -e ... ) subshell, so a failure anywhere would still stop the run. The ordering is the fail-fast rule from lesson 7-3, with the cheapest checks in front of the expensive image work.

A pull request with failing tests

Walking the ship.yml logic, the test job fails on the pull request, the deploy job never runs, and production is untouched.

pull_request triggers the test job, which goes red. The deploy job requires needs: test to pass and github.ref to be main, and a pull request satisfies neither condition.

GateSatisfied by a failing PR
needs: testno, the tests failed
if: github.ref == mainno, it is a branch

The author sees the red ✗, fixes the code, and only a green merge to main can ship. That is the entire safety model working as designed, with two independent gates rather than one.

The rollback command for notesy

The command is ./scripts/deploy.sh 41a9c02, since the deploy script takes a commit sha as its argument.

Every commit's image is still in GHCR under its sha tag, so rolling back is just deploying the old tag. The server pulls ghcr.io/ada/notesy:41a9c02 and compose swaps the containers in seconds.

ActionTimeLesson
deploy the previous shaseconds8-3
rebuild from a revert commitminutesavoid during an incident

After service is restored, the bug gets fixed calmly, merged, and the pipeline ships the repair through the same path.

That closes the course. You have containerized an app, wired it to a database, pushed images to a registry, built a pipeline that tests and ships them, and rehearsed the recovery when a deploy goes wrong.