ЁЯПл The SchoolтА║ЁЯЫдя╕П Platform EngineeringтА║ЁЯЪЪ рдзрдбрд╛ 07 тАФ CI/CD рдЪрд╛ рдмрд╛рдВрдзрд▓реЗрд▓рд╛ рд░рд╕реНрддрд╛: production рдХрдбреЗ рдЬрд╛рдгрд╛рд░рд╛ рдПрдХрдЪ рд╕рд╛рдорд╛рдпрд┐рдХ рд░рд╕реНрддрд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЪЪ рдзрдбрд╛ 07 тАФ CI/CD рдЪрд╛ рдмрд╛рдВрдзрд▓реЗрд▓рд╛ рд░рд╕реНрддрд╛: production рдХрдбреЗ рдЬрд╛рдгрд╛рд░рд╛ рдПрдХрдЪ рд╕рд╛рдорд╛рдпрд┐рдХ рд░рд╕реНрддрд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 07 ┬╖ рдорд╛рдЧреЗ: lesson-06-preview-environments ┬╖ рдкреБрдвреЗ: lesson-08-secrets-config


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ06, рдЖрдгрд┐ рдкреБрдиреНрд╣рд╛ рд╡рд╛рдкрд░рддрд╛ рдпреЗрдгрд╛рд░реЗ, version рдЕрд╕рд▓реЗрд▓реЗ pipeline templates. рдкреНрд░рддреНрдпреЗрдХ service рдиреЗ build-test-scan-deploy рдкрд╛рдпрд▒реНрдпрд╛рдВрдЪреА рд╕реНрд╡рддрдГрдЪреА copy рдмрд╛рд│рдЧрдгреНрдпрд╛рдРрд╡рдЬреА, рддреА рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪрд╛ template рдПрдХрд╛ version рд╡рд░ рдмреЛрд▓рд╛рд╡рддреЗ. Version reference рдХрд╕рд╛ рд╕реЛрдбрд╡рд▓рд╛ рдЬрд╛рддреЛ тАФ рд╣рд▓рдгрд╛рд░рд╛ major tag (@v2), рдиреЗрдордХреЗ version (@v2.0.0) рдХрд┐рдВрд╡рд╛ paste рдХреЗрд▓реЗрд▓реА copy тАФ рдпрд╛рд╡рд░реВрди рдард░рддреЗ рдХреА рджреБрд░реБрд╕реНрддреА service рдкрд░реНрдпрдВрдд рдЖрдкреЛрдЖрдк рдкреЛрд╣реЛрдЪрддреЗ рдХреА рдирд╛рд╣реА. idp/selfserve.py рдордзрд▓реЗ PIPELINES, resolve_ref() рдЖрдгрд┐ pipeline(), idp/demo.py рдордзрд▓реЗ pipelines().

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рд╡рд┐рджреНрдпрд╛рд░реНрдереА рдпреЗрдгреНрдпрд╛рдЖрдзреА рдкреНрд░рддреНрдпреЗрдХ рд╡рд░реНрдЧрдЦреЛрд▓реАрд▓рд╛ рд╕рдХрд╛рд│рдЪрд╛ рддреЛрдЪ рджрд┐рдирдХреНрд░рдо рд▓рд╛рдЧрддреЛ: рдХреБрд▓реВрдк рдЙрдШрдбрд╛, рдЖрдкрддреНрдХрд╛рд▓реАрди рджрд╛рд░ рддрдкрд╛рд╕рд╛, рджрд┐рд╡реЗ рд▓рд╛рд╡рд╛, рдЦрд┐рдбрдХреНрдпрд╛ рдЙрдШрдбрд╛. ЁЯЪк

рд╕реБрд░реБрд╡рд╛рддреАрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдиреЗ рдЖрдкрд▓реЗ рджрд┐рдирдХреНрд░рдо-рдХрд╛рд░реНрдб рд╕реНрд╡рддрдГ рд▓рд┐рд╣рд┐рд▓реЗ. рдордЧ рд╕реБрд╡рд┐рдзрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдиреЗ рдПрдХрдЪ рджрд┐рдирдХреНрд░рдо-рдХрд╛рд░реНрдб рд▓рд┐рд╣рд┐рд▓реЗ, рддреНрдпрд╛рд▓рд╛ рдХреНрд░рдорд╛рдВрдХ рджрд┐рд▓рд╛ (version 2), рдЖрдгрд┐ рддреЗ рд╢рд┐рдХреНрд╖рдХ-рдХрдХреНрд╖рд╛рдд рд▓рд╛рд╡рд▓реЗ. рдЖрддрд╛ рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдореНрд╣рдгрддрд╛рдд "рдореА рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреЗ рдХрд╛рд░реНрдб, version 2, рдкрд╛рд│рддреЗ."

рдХрд╛рд░реНрдпрд╛рд▓рдп рдПрдХ рдкрд╛рдпрд░реА рдЬреЛрдбрддреЗ тАФ "рдзреБрд░рд╛рдЪрд╛ alarm рддрдкрд╛рд╕рд╛" (version 2.1) тАФ рддреЗрд╡реНрд╣рд╛ рдХрд╛рд╣реАрддрд░реА рдордиреЛрд░рдВрдЬрдХ рдШрдбрддреЗ:

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    subgraph office["ЁЯПв the office's pipeline releases"]
      v1["v1.0.0<br/>4 steps ┬╖ 14 min"]
      v2["v2.0.0<br/>+ SBOM + image scan<br/>6 steps ┬╖ 17 min"]
      v21["v2.1.0<br/>+ dependency cache<br/>7 steps ┬╖ 15 min"]
    end
    er["exam-results @v2"] --> v21
    tt["timetable @v2.0.0"] --> v2
    ls["library-search @v1"] --> v1
    fe["fees: pasted copy of v1"] -.-> cp["ЁЯУД v1.0.0 copy<br/>never changes"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/platform-engineering/lesson-diagrams.html#l07

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдПрдХрдореЗрдХрд╛рдВрдХрдбреВрди copy рдХреЗрд▓реЗрд▓реНрдпрд╛ рд╢рдВрднрд░ pipelines рд╢рдВрднрд░ рдереЛрдбреНрдпрд╛ рд╡реЗрдЧрд│реНрдпрд╛ pipelines рдмрдирддрд╛рдд. рдирд╡рд╛ рдирд┐рдпрдо рдЖрд▓рд╛ ("рдкреНрд░рддреНрдпреЗрдХ image scan рдЭрд╛рд▓реАрдЪ рдкрд╛рд╣рд┐рдЬреЗ") рдХреА рдХреЛрдгрд╛рд▓рд╛ рддрд░реА рд╢рдВрднрд░ files рдмрджрд▓рд╛рд╡реНрдпрд╛ рд▓рд╛рдЧрддрд╛рдд. рд╕рд╛рдорд╛рдпрд┐рдХ template рдЕрд╕реЗрд▓ рддрд░ рдХрд╛рд░реНрдпрд╛рд▓рдп рдмрджрд▓ рдПрдХрджрд╛рдЪ рдХрд░рддреЗ, рддреЛ release рдХрд░рддреЗ, рдЖрдгрд┐ template рдкрд╛рд│рдгрд╛рд▒реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ service рд▓рд╛ рддреЛ рдорд┐рд│рддреЛ тАФ moving tags рд╕рд╛рдареА рд▓рдЧреЗрдЪ, exact pins рд╕рд╛рдареА рдкреБрдврдЪреНрдпрд╛ bump рд▓рд╛. Version reference рдореНрд╣рдгрдЬреЗ рдХреЗрд╡реНрд╣рд╛ рдпрд╛рд╡рд░рдЪреЗ team рдЪреЗ рдирд┐рдпрдВрддреНрд░рдг.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

idp/selfserve.py рдордзрд▓реНрдпрд╛ PIPELINES рдордзреНрдпреЗ рддреАрди releases (step, minutes) рдЪреНрдпрд╛ рдпрд╛рджреА рдореНрд╣рдгреВрди рдЖрд╣реЗрдд тАФ рд╢рд┐рдХрд╡рдгреНрдпрд╛рд╕рд╛рдареАрдЪреЗ рдЖрдХрдбреЗ. resolve_ref(ref) reference рд▓рд╛ release рдордзреНрдпреЗ рдмрджрд▓рддреЗ: exact version рд╕реНрд╡рддрдГрд▓рд╛рдЪ рдкрд░рдд рджреЗрддреЗ; v2 рд╕рд╛рд░рдЦрд╛ prefix рдЬреБрд│рдгрд╛рд░реА рдирд╡реАрдирддрдо release рдкрд░рдд рджреЗрддреЛ (рдЖрдХрдбреЗ рдореНрд╣рдгреВрди рддреБрд▓рдирд╛ рдХрд░реВрди, рдореНрд╣рдгрдЬреЗ v2.10.0 рд╣реЗ v2.9.0 рд╡рд░ рдорд╛рдд рдХрд░реЗрд▓); copy paste рдХреЗрд▓реЗрд▓реА v1.0.0 рдкрд░рдд рджреЗрддреЗ. pipeline(ref) release, рддрд┐рдЪреНрдпрд╛ рдкрд╛рдпрд▒реНрдпрд╛ рдЖрдгрд┐ рддрд┐рдЪреА рдПрдХреВрдг рдорд┐рдирд┐рдЯреЗ рдкрд░рдд рджреЗрддреЗ. idp/demo.py рдордзрд▓реЗ pipelines() рдЪрд╛рд░ services рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдЦрд░реЛрдЦрд░ рдХрд╛рдп рдЪрд╛рд▓рд╡рддреЗ рддреЗ рджрд╛рдЦрд╡рддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 idp/demo.py pipelines
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); import selfserve
from selfserve import PIPELINES, resolve_ref, pipeline
PIPELINES["v2.2.0"] = PIPELINES["v2.1.0"] + [("smoke test", 2)]      # the office ships a new minor release
PIPELINES["v3.0.0"] = [("checkout", 1), ("test", 3), ("build image", 4), ("sign image", 1), ("deploy", 3)]
for ref in ("v2", "v2.0.0", "v3", "v1", "copy"):
    version, steps, minutes = pipeline(ref)
    print(f"@{ref:<7} тЖТ {version:<21} {minutes:>2} min ┬╖ last step: {steps[-1]}")
print("unknown ref:", resolve_ref("v9"))
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

pipelines рд╣реЗ рдЫрд╛рдкрддреЗ:

   exam-results    uses @v2      тЖТ runs v2.1.0                7 steps, 15 min  (scans images)
   timetable       uses @v2.0.0  тЖТ runs v2.0.0                6 steps, 17 min  (scans images)
   library-search  uses @v1      тЖТ runs v1.0.0                4 steps, 14 min
   fees            uses @copy    тЖТ runs v1.0.0 (pasted copy)  4 steps, 14 min

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

@v2      тЖТ v2.2.0                17 min ┬╖ last step: smoke test
@v2.0.0  тЖТ v2.0.0                17 min ┬╖ last step: deploy
@v3      тЖТ v3.0.0                12 min ┬╖ last step: deploy
@v1      тЖТ v1.0.0                14 min ┬╖ last step: deploy
@copy    тЖТ v1.0.0 (pasted copy)  14 min ┬╖ last step: deploy

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдПрдХрд╛ рдирд╡реНрдпрд╛ release (v2.2.0) рдиреЗ @v2 рдХрд╛рдп рдЪрд╛рд▓рд╡рддреЗ рддреЗ рдмрджрд▓рд▓реЗ тАФ рдПрдХ smoke test рдЖрд▓реА тАФ рдкрдг @v2.0.0 рдЬрд╛рдЧреАрдЪ рд░рд╛рд╣рд┐рд▓реЗ. Major release v3.0.0 рдХреЛрдгреА v3 рдорд╛рдЧреЗрдкрд░реНрдпрдВрдд рдХреЛрдгрд╛рдкрд░реНрдпрдВрддрдЪ рдкреЛрд╣реЛрдЪрд▓реА рдирд╛рд╣реА; major version рдЪрд╛ рд╣рд╛рдЪ рддрд░ рдЙрджреНрджреЗрд╢ рдЖрд╣реЗ. рджреЛрди services (library-search, fees) рдХреЛрдгрддрд╛рдЪ image scan рдЪрд╛рд▓рд╡рдд рдирд╛рд╣реАрдд. рддреНрдпрд╛рддрд▓реА рдПрдХ рдПрдХрдЪ reference @v2 рдХрд░реВрди рддреЗ рджреБрд░реБрд╕реНрдд рдХрд░реВ рд╢рдХрддреЗ; рджреБрд╕рд░реАрд▓рд╛ рдЖрдзреА рдЖрдкрд▓реА copy рд╣рдЯрд╡рд╛рд╡реА рд▓рд╛рдЧреЗрд▓. Demo рдордзреНрдпреЗ, v2.1.0 рдЪреНрдпрд╛ cache рдореБрд│реЗ рдЬрд╛рд╕реНрдд рдкрд╛рдпрд▒реНрдпрд╛ рдЕрд╕реВрдирд╣реА pipeline v2.0.0 рдкреЗрдХреНрд╖рд╛ 2 рдорд┐рдирд┐рдЯреЗ рдЬрд▓рдж рдЭрд╛рд▓реА тАФ platform release рдлрдХреНрдд рдирд┐рдпрдо рдЬреЛрдбрдд рдирд╛рд╣реА, рд╡реЗрд│рд╣реА рдкрд░рдд рджреЗрдК рд╢рдХрддреЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ repo рд╡рд░ GitHub Actions. рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪрд╛ reusable workflow, school/paved-road рдордзреНрдпреЗ:

# .github/workflows/python.yml
on:
  workflow_call:
    inputs:
      image-name: {required: true, type: string}
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt && pytest
      - run: docker build -t "${{ inputs.image-name }}:${{ github.sha }}" .

Service рддреЛ рдмреЛрд▓рд╛рд╡рддреЗ тАФ moving major tag рдиреЗ, рдХрд┐рдВрд╡рд╛ exact pin рд╕рд╛рдареА рдкреВрд░реНрдг commit SHA рдиреЗ:

jobs:
  ci:
    uses: school/paved-road/.github/workflows/python.yml@v2
    with:
      image-name: exam-results

GitLab CI рд╣реЗрдЪ include рдиреЗ рдХрд░рддреЗ:

include:
  - project: school/paved-road
    ref: v2.1.0
    file: /templates/python.yml

Dependabot рдХрд┐рдВрд╡рд╛ Renovate рд╣реЗ references bump рдХрд░рдгрд╛рд░реЗ pull requests рдЙрдШрдбреВ рд╢рдХрддрд╛рдд, рдореНрд╣рдгрдЬреЗ exact pins рдЧреБрдкрдЪреВрдк рдорд╛рдЧреЗ рдкрдбрдд рдирд╛рд╣реАрдд.

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ service рдХреЛрдгрддреЗ pipeline version рдЪрд╛рд▓рд╡рддреЗ рдпрд╛рдЪреА рдпрд╛рджреА рдХрд░рд╛ (рдзрдбрд╛ 10 рддреЗ scorecard check рдордзреНрдпреЗ рдмрджрд▓рддреЛ). Copies рдЖрдгрд┐ рд╕рд░реНрд╡рд╛рдд рдЬреБрдиреЗ pins рдЗрдереЗрдЪ рдкреБрдврдЪреА security finding рдпреЗрдКрди рдкрдбреЗрд▓.

тПня╕П рдкреБрдвреЗ

Pipelines рдЖрдгрд┐ services рдирд╛ passwords рдЖрдгрд┐ settings рд▓рд╛рдЧрддрд╛рдд. рдкреБрдвреЗ: рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреЗ рдХрд┐рд▓реНрд▓реНрдпрд╛рдВрдЪреЗ рдХрдкрд╛рдЯ тАФ service рдореНрд╣рдгреВрди secrets рдЖрдгрд┐ config.

git checkout lesson-08-secrets-config

ЁЯЪЪ Lesson 07 тАФ The paved CI/CD road: one shared road to production

ЁЯУН You are here: Lesson 07 of 12 ┬╖ Previous: lesson-06-preview-environments ┬╖ Next: lesson-08-secrets-config


ЁЯУж What's in this branch

Lessons 01тАУ06, plus reusable, versioned pipeline templates. Instead of each service carrying its own copy of the build-test-scan-deploy steps, it calls the office's template at a version. How a version reference is resolved тАФ a moving major tag (@v2), an exact version (@v2.0.0) or a pasted copy тАФ decides whether a fix reaches the service by itself. PIPELINES, resolve_ref() and pipeline() in idp/selfserve.py, pipelines() in idp/demo.py.

ЁЯзТ Explain like I'm 5

Every classroom needs the same morning routine before students come in: unlock, check the fire exit, switch on the lights, open the windows. ЁЯЪк

At first each teacher wrote her own routine card. Then the facilities office wrote one routine card, numbered it (version 2), and pinned it in the staff room. Teachers now say "I follow the office's card, version 2."

When the office adds a step тАФ "check the smoke alarm" (version 2.1) тАФ something interesting happens:

ЁЯЧ║я╕П Diagram

flowchart LR
    subgraph office["ЁЯПв the office's pipeline releases"]
      v1["v1.0.0<br/>4 steps ┬╖ 14 min"]
      v2["v2.0.0<br/>+ SBOM + image scan<br/>6 steps ┬╖ 17 min"]
      v21["v2.1.0<br/>+ dependency cache<br/>7 steps ┬╖ 15 min"]
    end
    er["exam-results @v2"] --> v21
    tt["timetable @v2.0.0"] --> v2
    ls["library-search @v1"] --> v1
    fe["fees: pasted copy of v1"] -.-> cp["ЁЯУД v1.0.0 copy<br/>never changes"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/platform-engineering/lesson-diagrams.html#l07

тЭУ What

ЁЯдФ Why

Because a hundred pipelines copied from each other become a hundred slightly different pipelines. When a new rule arrives ("every image must be scanned"), someone must edit a hundred files. With a shared template, the office makes the change once, releases it, and every service that follows the template gets it тАФ immediately for moving tags, at the next bump for exact pins. The version reference is the team's control over when.

ЁЯФз How (in this repo)

PIPELINES in idp/selfserve.py holds three releases as lists of (step, minutes) тАФ teaching numbers. resolve_ref(ref) turns a reference into a release: an exact version returns itself; a prefix like v2 returns the newest matching release (compared as numbers, so v2.10.0 would beat v2.9.0); copy returns the pasted v1.0.0. pipeline(ref) returns the release, its steps and its total minutes. pipelines() in idp/demo.py shows four services and what each really runs.

ЁЯзк Try it

python3 idp/demo.py pipelines
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); import selfserve
from selfserve import PIPELINES, resolve_ref, pipeline
PIPELINES["v2.2.0"] = PIPELINES["v2.1.0"] + [("smoke test", 2)]      # the office ships a new minor release
PIPELINES["v3.0.0"] = [("checkout", 1), ("test", 3), ("build image", 4), ("sign image", 1), ("deploy", 3)]
for ref in ("v2", "v2.0.0", "v3", "v1", "copy"):
    version, steps, minutes = pipeline(ref)
    print(f"@{ref:<7} тЖТ {version:<21} {minutes:>2} min ┬╖ last step: {steps[-1]}")
print("unknown ref:", resolve_ref("v9"))
EOF

тЬЕ Verify тАФ what you should see

pipelines prints:

   exam-results    uses @v2      тЖТ runs v2.1.0                7 steps, 15 min  (scans images)
   timetable       uses @v2.0.0  тЖТ runs v2.0.0                6 steps, 17 min  (scans images)
   library-search  uses @v1      тЖТ runs v1.0.0                4 steps, 14 min
   fees            uses @copy    тЖТ runs v1.0.0 (pasted copy)  4 steps, 14 min

Your snippet prints:

@v2      тЖТ v2.2.0                17 min ┬╖ last step: smoke test
@v2.0.0  тЖТ v2.0.0                17 min ┬╖ last step: deploy
@v3      тЖТ v3.0.0                12 min ┬╖ last step: deploy
@v1      тЖТ v1.0.0                14 min ┬╖ last step: deploy
@copy    тЖТ v1.0.0 (pasted copy)  14 min ┬╖ last step: deploy

ЁЯПБ What you just proved

One new release (v2.2.0) changed what @v2 runs тАФ a smoke test appeared тАФ while @v2.0.0 stayed put. The major release v3.0.0 reached nobody until a caller asks for v3; that is the point of a major version. Two services (library-search, fees) run no image scan. One of them can fix that by changing one reference to @v2; the other must first delete its copy. In the demo, v2.1.0's cache made the pipeline 2 minutes faster than v2.0.0 even with more steps тАФ a platform release can give time back, not only add rules.

тЪая╕П Common mistakes

ЁЯПн In production

GitHub Actions, on a real repo. The office's reusable workflow, in school/paved-road:

# .github/workflows/python.yml
on:
  workflow_call:
    inputs:
      image-name: {required: true, type: string}
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt && pytest
      - run: docker build -t "${{ inputs.image-name }}:${{ github.sha }}" .

A service calls it тАФ a moving major tag, or a full commit SHA for an exact pin:

jobs:
  ci:
    uses: school/paved-road/.github/workflows/python.yml@v2
    with:
      image-name: exam-results

GitLab CI does the same with include:

include:
  - project: school/paved-road
    ref: v2.1.0
    file: /templates/python.yml

Dependabot or Renovate can open pull requests that bump these references, so exact pins do not quietly fall behind.

ЁЯПн Why this matters in production: list which pipeline version every service runs (lesson 10 turns that into a scorecard check). The copies and the oldest pins are where the next security finding will land.

тПня╕П Next

Pipelines and services need passwords and settings. Next: the office's key cabinet тАФ secrets and config as a service.

git checkout lesson-08-secrets-config
тЖР Previouspreview environmentsNext тЖТsecrets config

This page is the lesson's README from the lesson-07-paved-pipelines branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.