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

ЁЯПЫя╕П рдзрдбрд╛ 12 тАФ Developer portal рдЖрдгрд┐ рд╕рдВрдкреВрд░реНрдг рдЪрд┐рддреНрд░: рдПрдХ рд╕реНрд╡рд╛рдЧрдд рдХрдХреНрд╖

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 12 ┬╖ рдорд╛рдЧреАрд▓: lesson-11-measuring ┬╖ рд╢реЗрд╡рдЯрдЪрд╛ рдзрдбрд╛ ЁЯОУ


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

рдзрдбреЗ 01тАУ11, рдЖрдгрд┐ internal developer portal: рдкреНрд░рддреНрдпреЗрдХ service рдЪреЗ рдПрдХ рдкрд╛рди, рдЬреЗ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рдмрд╛рдХреАрдЪреНрдпрд╛ рднрд╛рдЧрд╛рдВрдирд╛ рдорд╛рд╣реАрдд рдЕрд╕рд▓реЗрд▓реЗ рд╕рдЧрд│реЗ рдЧреЛрд│рд╛ рдХрд░рддреЗ тАФ owner, dependencies рдЖрдгрд┐ dependents (рдзрдбрд╛ 04), scorecard (10), policy findings (09), pipeline version (07), preview environments (06) рдЖрдгрд┐ DORA рдЖрдХрдбреЗ (11). рдЖрдгрд┐ рдХреЛрд░реНрд╕рдЪрд╛ рд╕рдВрдкреВрд░реНрдг рдирдХрд╛рд╢рд╛. portal_page() idp/guard.py рдордзреНрдпреЗ, portal() idp/demo.py рдордзреНрдпреЗ.

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

рд╕реБрд╡рд┐рдзрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдп рдореЛрдареЗ рдЭрд╛рд▓реЗ рдЖрд╣реЗ: kit рдЪреЗ рдХрдкрд╛рдЯ, рдиреЛрдВрджрд╡рд╣реА, menu, рдЪрд╛рдЪрдгреА рд╡рд░реНрдЧрдЦреЛрд▓реНрдпрд╛, рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдХрд╛рдорд╛рдВрдЪреА cards, рдХрд┐рд▓реНрд▓реНрдпрд╛рдВрдЪреЗ рдХрдкрд╛рдЯ, рд╕реБрд░рдХреНрд╖рд╛ рддрдкрд╛рд╕рдгреА-рдпрд╛рджреА, рддрдпрд╛рд░реА-cards рдЖрдгрд┐ рдЖрдХрдбреНрдпрд╛рдВрдЪреА рд╡рд╣реА. рдирд╡реНрдпрд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрд▓рд╛ рддреА рд╕рдЧрд│реА рд╢реЛрдзрд╛рдпрд▓рд╛рдЪ рдирдХрд╛рд╢рд╛ рд▓рд╛рдЧреЗрд▓.

рдореНрд╣рдгреВрди рджреАрдкрд┐рдХрд╛ рдПрдХ рд╕реНрд╡рд╛рдЧрдд рдХрдХреНрд╖ рд╕реБрд░реВ рдХрд░рддреЗ. ЁЯПЫя╕П рдХрддрд░рд┐рдирд╛ рддрд┐рдереЗ рдпреЗрддреЗ рдЖрдгрд┐ рдореНрд╣рдгрддреЗ "exam-results". рдХрдХреНрд╖ рдПрдХ рдкрд╛рди рджрд╛рдЦрд╡рддреЛ:

рд╕реНрд╡рд╛рдЧрдд рдХрдХреНрд╖ рд╕реНрд╡рддрдГ рдХрд╛рдо рдХрд░рдд рдирд╛рд╣реА. рддреЗ kits, menu рдЖрдгрд┐ рдХрдкрд╛рдЯ рдХрд░рддрд╛рдд. рдХрдХреНрд╖ рддреНрдпрд╛рдВрдирд╛ рдПрдХрд╛ рдЬрд╛рдЧреА, рд╕рдЧрд│реНрдпрд╛рдВрд╕рд╛рдареА рдПрдХрд╛рдЪ рдкрджреНрдзрддреАрдиреЗ, рд╕рд╣рдЬ рд╕рд╛рдкрдбрддреАрд▓ рдЕрд╕реЗ рдХрд░рддреЛ.

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

flowchart LR
    cat["ЁЯУЗ catalog (04)"] --> p["ЁЯПЫя╕П portal page<br/>exam-results"]
    sc["ЁЯПЕ scorecard (10)<br/>92% gold"] --> p
    po["ЁЯЫбя╕П policy (09)<br/>1 warning"] --> p
    pi["ЁЯЪЪ pipeline (07)<br/>v2 тЖТ v2.1.0"] --> p
    pr["ЁЯзк previews (06)"] --> p
    do["ЁЯУК DORA (11)<br/>3.0/week ┬╖ 5.0 h"] --> p
    p --> t["ЁЯЫдя╕П templates (03) and<br/>self-service (05, 08)<br/>one click away"]

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рднрд╛рдЧрд╛рдиреЗ рдПрдХ рдкреНрд░рд╢реНрди рд╕реЛрдбрд╡рд▓рд╛, рдЖрдгрд┐ рд╕рдЧрд│реНрдпрд╛рдВрдиреА рдорд┐рд│реВрди рдПрдХ рдирд╡рд╛ рдкреНрд░рд╢реНрди рдирд┐рд░реНрдорд╛рдг рдХреЗрд▓рд╛: рдкрд╛рд╣рд╛рдпрд▓рд╛ рдЦреВрдк рдЬрд╛рдЧрд╛. Portal platform рд▓рд╛ рдЕрд╕реЗ рдмрдирд╡рддреЛ рдХреА рдирд╡реА рд╕рд╣рдХрд╛рд░реА рдкрд╣рд┐рд▓реНрдпрд╛рдЪ рджрд┐рд╡рд╢реА рддреЗ рд╢реЛрдзреВ рд╢рдХрддреЗ. рдкреНрд░рддреНрдпреЗрдХ team рдЖрдкрд▓реА service рдкрд╛рд╣рдд рдЕрд╕рддрд╛рдирд╛рдЪ рддреЛ рддрд┐рд▓рд╛ рддрд┐рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдЙрдгрд┐рд╡рд╛ (scorecard, warnings) рджрд╛рдЦрд╡рддреЛ. Portal рд╢реЗрд╡рдЯреА рдмрдирд╡рдгреЗ рд╣реЗ рдЬрд╛рдгреВрдирдмреБрдЬреВрди рдЖрд╣реЗ: portal рддреНрдпрд╛рдорд╛рдЧрдЪреНрдпрд╛ catalog, templates рдЖрдгрд┐ data рдЗрддрдХрд╛рдЪ рдЪрд╛рдВрдЧрд▓рд╛ рдЕрд╕рддреЛ.

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

idp/guard.py рдордзреАрд▓ portal_page(name, catalog, passed, manifest, pipeline_ref, previews_live, deploys, weeks, resolve) рдЖрдзреАрдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдзрдбреНрдпрд╛рддреАрд▓ рднрд╛рдЧ call рдХрд░рддреЛ: owner, dependencies рдЖрдгрд┐ dependents рд╕рд╛рдареА Catalog, card рд╕рд╛рдареА score(), warnings рд╕рд╛рдареА audit mode рдордзреНрдпреЗ evaluate(), pipeline рд╕рд╛рдареА resolve_ref(), рдЖрдгрд┐ рдЖрдХрдбреНрдпрд╛рдВрд╕рд╛рдареА dora(). рддреЛ рд╕рд╣рд╛ рдУрд│реА рдкрд░рдд рджреЗрддреЛ тАФ рддреЗрдЪ рдкрд╛рди. idp/demo.py рдордзреАрд▓ portal() exam-results рдЪреЗ рдкрд╛рди рдЫрд╛рдкрддреЛ.

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

python3 idp/demo.py portal
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); from demo import school_catalog, AFTER, BEFORE
from guard import CHECKS, portal_page; from selfserve import resolve_ref
c = school_catalog()
passed = {n for n, _ in CHECKS} - {"SLO written", "has a runbook"}
m = dict(labels={"owner": "maths"}, image="timetable:latest", limits={"cpu": "250m", "memory": "128Mi"}, env="production", replicas=2)
for line in portal_page("timetable", c, passed, m, "v2.0.0", 0, BEFORE, 4, resolve_ref): print(line)
EOF
python3 idp/demo.py
python3 idp/test_idp.py

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

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

тФАтФА the portal page for exam-results (one place, gathered from every part of the office):
   owner science ┬╖ lifecycle production ┬╖ depends on results-db, auth
   depended on by parent-portal
   scorecard 92% (gold) ┬╖ next: SLO written
   policy: allowed with warnings ┬╖ bucket is public тЖТ set public_bucket: false and serve files through the CDN kit
   pipeline v2 тЖТ v2.1.0 ┬╖ preview environments live: 1
   DORA: 3.0 deploys/week ┬╖ lead time 5.0 h ┬╖ failure rate 8% ┬╖ recovery 2.0 h

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

owner maths ┬╖ lifecycle production ┬╖ depends on auth
depended on by parent-portal
scorecard 83% (silver) ┬╖ next: has a runbook
policy: allowed with warnings ┬╖ image tag is 'latest' or missing тЖТ pin a version such as :1.4.2 (the paved pipeline tags every build)
pipeline v2.0.0 тЖТ v2.0.0 ┬╖ preview environments live: 0
DORA: 1.0 deploys/week ┬╖ lead time 150.0 h ┬╖ failure rate 50% ┬╖ recovery 23.0 h

рдкреВрд░реНрдг run тЬЕ done тАФ every teacher has a classroom рдиреЗ рд╕рдВрдкрддреЛ, рдЖрдгрд┐ tests 12/12 passed рдиреЗ.

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

рдПрдХрд╛ function call рдиреЗ рд╕рд╣рд╛ рдзрдбреНрдпрд╛рдВрдЪреА рдЙрддреНрддрд░реЗ рдЧреЛрд│рд╛ рдХреЗрд▓реА. Timetable рдЪреЗ рдкрд╛рди maths team рд▓рд╛ рдХреЛрдгреАрд╣реА report рди рд▓рд┐рд╣рд┐рддрд╛ рддреАрди рдХрд╛рдореЗ рд╕рд╛рдВрдЧрддреЗ: runbook рдЬреЛрдбрд╛, image pin рдХрд░рд╛, рдЖрдгрд┐ v2.0.0 рд╡рд░реВрди @v2 рд╡рд░ рдЬрд╛рдгреНрдпрд╛рдЪрд╛ рд╡рд┐рдЪрд╛рд░ рдХрд░рд╛. рддреНрдпрд╛рдЪреА DORA рдУрд│ ("before" data рд╡рд╛рдкрд░реВрди) рддреА рдХреБрдареВрди рд╕реБрд░реБрд╡рд╛рдд рдХрд░реЗрд▓ рддреЗ рджрд╛рдЦрд╡рддреЗ. рдкрд╛рдирд╛рд╡рд░рдЪреЗ рдкреНрд░рддреНрдпреЗрдХ value рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рд╕реНрд╡рддрдВрддреНрд░рдкрдгреЗ рдХрд╛рдо рдХрд░рдгрд╛рд▒реНрдпрд╛ рдПрдЦрд╛рджреНрдпрд╛ рднрд╛рдЧрд╛рддреВрди рдЖрд▓реЗ; portal рддреЗ рдлрдХреНрдд рд╡рд╛рдЪрддреЛ. рд╣реАрдЪ рд░рдЪрдирд╛ рдЖрд╣реЗ: portal рд╣рд╛ рд╕реНрд╡рд╛рдЧрдд рдХрдХреНрд╖ рдЖрд╣реЗ, рдХрд╛рд░реНрдпрд╛рд▓рдп рдирд╛рд╣реА.

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

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

рдЦрд▒реНрдпрд╛ machine рд╡рд░ Backstage app рд╕реБрд░реВ рдХрд░рдгреЗ (Node.js рдЖрдгрд┐ Yarn рд▓рд╛рдЧрддрд╛рдд):

npx @backstage/create-app@latest          # asks for a name, creates the app
cd school-portal && yarn start            # recent templates start frontend + backend (older ones used yarn dev)

рдзрдбрд╛ 04 рдордзреАрд▓ catalog files app-config.yaml рдордзреНрдпреЗ register рдХрд░рдгреЗ:

catalog:
  locations:
    - type: url
      target: https://github.com/school/exam-results/blob/main/catalog-info.yaml
    - type: url
      target: https://github.com/school/paved-road/blob/main/templates/python-service/template.yaml
      rules:
        - allow: [Template]

Service рд▓рд╛ рддрд┐рдЪреНрдпрд╛ docs (TechDocs) рдЖрдгрд┐ CI рд╢реА рдЬреЛрдбрдгреЗ рддрд┐рдЪреНрдпрд╛ catalog-info.yaml рдордзреАрд▓ annotations рдиреЗ рд╣реЛрддреЗ, рдЬрд╕реЗ backstage.io/techdocs-ref: dir:. рдЖрдгрд┐ github.com/project-slug: school/exam-results.

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: platform рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдЗрддрд░ рднрд╛рдЧрд╛рдкреНрд░рдорд╛рдгреЗ portal рд▓рд╛рд╣реА owner, roadmap рдЖрдгрд┐ users рджреНрдпрд╛ (рдзрдбрд╛ 02). рддреЛрд╣реА рдПрдХ product рдЖрд╣реЗ.

ЁЯОУ рдкреНрд░рддреНрдпреЗрдХ рд╢рд┐рдХреНрд╖рд┐рдХреЗрд▓рд╛ рд╡рд░реНрдЧрдЦреЛрд▓реА рдЖрд╣реЗ

рдХрд╛рдорд╛рдВрдЪреНрдпрд╛ рдУрдЭреНрдпрд╛рдЦрд╛рд▓реА рдмреБрдбрд╛рд▓реЗрд▓реНрдпрд╛ teams рдЖрдгрд┐ рд╡рд╛рдврддреА ticket рдЪреА рд░рд╛рдВрдЧ тЖТ product рдкреНрд░рдорд╛рдгреЗ рдЪрд╛рд▓рд╡рд▓реЗрд▓рд╛ platform, рд╕рд░реНрд╡рд╛рдд рдореЛрдареНрдпрд╛ рддреНрд░рд╛рд╕рд╛рдкрд╛рд╕реВрди рд╕реБрд░реБрд╡рд╛рдд рдХрд░реВрди рдмрдирд╡рд▓реЗрд▓рд╛ тЖТ service рдпреЛрдЧреНрдп рдкрджреНрдзрддреАрдиреЗ рд╕реБрд░реВ рдХрд░рдгрд╛рд░реЗ golden-path templates тЖТ рдкреНрд░рддреНрдпреЗрдХ owner рдЖрдгрд┐ blast radius рдорд╛рд╣реАрдд рдЕрд╕рд▓реЗрд▓рд╛ catalog тЖТ quotas рдордзрд▓реЗ self-service resources тЖТ рд╕реНрд╡рддрдГрд╣реВрди рд╕рд╛рдл рд╣реЛрдгрд╛рд░реА preview environments тЖТ рдПрдХрдЪ versioned pipeline рд░рд╕реНрддрд╛ тЖТ versions рдЖрдгрд┐ рд╕реНрдкрд╖реНрдЯреАрдХрд░рдгрд╛рдВрд╕рд╣ secrets рдЖрдгрд┐ config тЖТ рджреБрд░реБрд╕реНрддреА рдХрд╢реА рдХрд░рд╛рдпрдЪреА рддреЗ рд╕рд╛рдВрдЧрдгрд╛рд░реЗ guardrails тЖТ рдкреБрдврдЪреА рдкрд╛рдпрд░реА рджреЗрдгрд╛рд░реЗ scorecards тЖТ рд▓реЛрдХрд╛рдВрдирд╛рд╣реА рд╡рд┐рдЪрд╛рд░реВрди рдХреЗрд▓реЗрд▓реЗ рдкреНрд░рд╛рдорд╛рдгрд┐рдХ рдореЛрдЬрдорд╛рдк тЖТ рдЖрдгрд┐ рд╕рдЧрд│реЗ рдПрдХрддреНрд░ рдЖрдгрдгрд╛рд░рд╛ рдПрдХ рд╕реНрд╡рд╛рдЧрдд рдХрдХреНрд╖. рддреБрдореНрд╣реА рдлрдХреНрдд platform engineering рд╢рд┐рдХрд▓рд╛ рдирд╛рд╣реАрдд тАФ рддреБрдореНрд╣реА рдХреЛрдгрддреНрдпрд╛рд╣реА team рдЪрд╛ рджрд┐рд╡рд╕ рдкрд╛рд╣реВрди рдпреЛрдЧреНрдп рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░реВ рд╢рдХрддрд╛: рддреЗ рдХрд╢рд╛рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рдд рдЖрд╣реЗрдд, рддреЗ рдХрд╛рдп рдкреБрдиреНрд╣рд╛ рдкреБрдиреНрд╣рд╛ рдмрдирд╡рдд рдЖрд╣реЗрдд, рдЖрдгрд┐ рд╕реБрд░рдХреНрд╖рд┐рдд рдорд╛рд░реНрдЧрд╛рд▓рд╛ рд╕реЛрдкрд╛ рдорд╛рд░реНрдЧ рдХрд╛рдп рдмрдирд╡реЗрд▓? ЁЯЫдя╕ПЁЯПЫя╕ПЁЯОУ

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

рдЗрддрд░ рд╢рд╛рд│рд╛. CI/CD рд╢рд╛рд│рд╛ pipelines рдордзреНрдпреЗ рдЦреЛрд▓рд╡рд░ рдЬрд╛рддреЗ; Argo CD рд╢рд╛рд│рд╛ GitOps delivery рдЖрдгрд┐ ApplicationSets рд╡рд░; Kubernetes рд╢рд╛рд│рд╛ рдмрд╣реБрддреЗрдХ platforms рдЬреНрдпрд╛рд╡рд░ рдЪрд╛рд▓рддрд╛рдд рддреНрдпрд╛рд╡рд░; Observability рд╢рд╛рд│рд╛ alerts рдЖрдгрд┐ dashboards kits рд╡рд░; Security рд╢рд╛рд│рд╛ guardrails рдЬреНрдпрд╛рдЪреЗ рд░рдХреНрд╖рдг рдХрд░рддрд╛рдд рддреНрдпрд╛рд╡рд░; School portal рдордзреНрдпреЗ рдмрд╛рдХреА рд╕рдЧрд│реЗ рдЖрд╣реЗ.

git checkout main
python3 idp/demo.py     # one last run, for fun

ЁЯПЫя╕П Lesson 12 тАФ The developer portal & the whole picture: one front desk

ЁЯУН You are here: Lesson 12 of 12 ┬╖ Previous: lesson-11-measuring ┬╖ The last lesson ЁЯОУ


ЁЯУж What's in this branch

Lessons 01тАУ11, plus the internal developer portal: one page per service that gathers what the rest of the office knows тАФ owner, dependencies and dependents (lesson 04), scorecard (10), policy findings (09), pipeline version (07), preview environments (06) and DORA numbers (11). And the whole map of the course. portal_page() in idp/guard.py, portal() in idp/demo.py.

ЁЯзТ Explain like I'm 5

The facilities office has grown: a kit shelf, a register, a menu, trial rooms, routine cards, a key cabinet, a safety checklist, readiness cards and a notebook of numbers. A new teacher would need a map just to find them.

So Dipika opens a front desk. ЁЯПЫя╕П Katrina walks up and says "exam-results". The desk shows one page:

The front desk does not do the work. The kits, the menu and the cabinet do that. The desk makes them easy to find, in one place, the same way for everyone.

ЁЯЧ║я╕П Diagram

flowchart LR
    cat["ЁЯУЗ catalog (04)"] --> p["ЁЯПЫя╕П portal page<br/>exam-results"]
    sc["ЁЯПЕ scorecard (10)<br/>92% gold"] --> p
    po["ЁЯЫбя╕П policy (09)<br/>1 warning"] --> p
    pi["ЁЯЪЪ pipeline (07)<br/>v2 тЖТ v2.1.0"] --> p
    pr["ЁЯзк previews (06)"] --> p
    do["ЁЯУК DORA (11)<br/>3.0/week ┬╖ 5.0 h"] --> p
    p --> t["ЁЯЫдя╕П templates (03) and<br/>self-service (05, 08)<br/>one click away"]

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

тЭУ What

ЁЯдФ Why

Because each part of the office solved a problem, and together they created a new one: too many places to look. The portal turns the platform into something a new teammate can discover on day one. It also shows each team its own gaps (scorecard, warnings) at the moment it is looking at its service. Building the portal last is deliberate: a portal is only as good as the catalog, templates and data behind it.

ЁЯФз How (in this repo)

portal_page(name, catalog, passed, manifest, pipeline_ref, previews_live, deploys, weeks, resolve) in idp/guard.py calls the pieces from every earlier lesson: Catalog for owner, dependencies and dependents, score() for the card, evaluate() in audit mode for warnings, resolve_ref() for the pipeline, and dora() for the numbers. It returns six lines тАФ the page. portal() in idp/demo.py prints the page for exam-results.

ЁЯзк Try it

python3 idp/demo.py portal
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); from demo import school_catalog, AFTER, BEFORE
from guard import CHECKS, portal_page; from selfserve import resolve_ref
c = school_catalog()
passed = {n for n, _ in CHECKS} - {"SLO written", "has a runbook"}
m = dict(labels={"owner": "maths"}, image="timetable:latest", limits={"cpu": "250m", "memory": "128Mi"}, env="production", replicas=2)
for line in portal_page("timetable", c, passed, m, "v2.0.0", 0, BEFORE, 4, resolve_ref): print(line)
EOF
python3 idp/demo.py
python3 idp/test_idp.py

тЬЕ Verify тАФ what you should see

portal prints:

тФАтФА the portal page for exam-results (one place, gathered from every part of the office):
   owner science ┬╖ lifecycle production ┬╖ depends on results-db, auth
   depended on by parent-portal
   scorecard 92% (gold) ┬╖ next: SLO written
   policy: allowed with warnings ┬╖ bucket is public тЖТ set public_bucket: false and serve files through the CDN kit
   pipeline v2 тЖТ v2.1.0 ┬╖ preview environments live: 1
   DORA: 3.0 deploys/week ┬╖ lead time 5.0 h ┬╖ failure rate 8% ┬╖ recovery 2.0 h

Your snippet prints:

owner maths ┬╖ lifecycle production ┬╖ depends on auth
depended on by parent-portal
scorecard 83% (silver) ┬╖ next: has a runbook
policy: allowed with warnings ┬╖ image tag is 'latest' or missing тЖТ pin a version such as :1.4.2 (the paved pipeline tags every build)
pipeline v2.0.0 тЖТ v2.0.0 ┬╖ preview environments live: 0
DORA: 1.0 deploys/week ┬╖ lead time 150.0 h ┬╖ failure rate 50% ┬╖ recovery 23.0 h

The full run ends with тЬЕ done тАФ every teacher has a classroom, and the tests with 12/12 passed.

ЁЯПБ What you just proved

One function call gathered six lessons' worth of answers. The timetable page tells the maths team three things to do without anyone writing them a report: add a runbook, pin the image, and consider moving from v2.0.0 to @v2. Its DORA line (using the "before" data) shows where it would start. Every value on the page came from a part of the office that works on its own; the portal only reads them. That is the design: the portal is the front desk, not the office.

тЪая╕П Common mistakes

ЁЯПн In production

Starting a Backstage app on a real machine (Node.js and Yarn required):

npx @backstage/create-app@latest          # asks for a name, creates the app
cd school-portal && yarn start            # recent templates start frontend + backend (older ones used yarn dev)

Registering the catalog files from lesson 04, in app-config.yaml:

catalog:
  locations:
    - type: url
      target: https://github.com/school/exam-results/blob/main/catalog-info.yaml
    - type: url
      target: https://github.com/school/paved-road/blob/main/templates/python-service/template.yaml
      rules:
        - allow: [Template]

Linking a service to its docs (TechDocs) and CI is done with annotations in its catalog-info.yaml, such as backstage.io/techdocs-ref: dir:. and github.com/project-slug: school/exam-results.

ЁЯПн Why this matters in production: give the portal an owner, a roadmap and users, like every other part of the platform (lesson 02). It is a product too.

ЁЯОУ Every teacher has a classroom

Teams drowning in concerns and a growing ticket line тЖТ a platform run as a product, built from the biggest pain down тЖТ golden-path templates that start a service right тЖТ a catalog that knows every owner and the blast radius тЖТ self-service resources within quotas тЖТ preview environments that clean up after themselves тЖТ one versioned pipeline road тЖТ secrets and config with versions and explanations тЖТ guardrails that say how to fix тЖТ scorecards that give the next step тЖТ honest measures, with people asked too тЖТ and one front desk that gathers it all. You didn't just learn platform engineering тАФ you can look at any team's day and ask the right questions: what are they waiting for, what are they rebuilding, and what would make the safe way the easy way? ЁЯЫдя╕ПЁЯПЫя╕ПЁЯОУ

тПня╕П Next

The other schools. The CI/CD school goes deeper on pipelines; the Argo CD school on GitOps delivery and ApplicationSets; the Kubernetes school on what most platforms run on; the Observability school on the alerts and dashboards kits; the Security school on what the guardrails protect; the School portal has the rest.

git checkout main
python3 idp/demo.py     # one last run, for fun
тЖР PreviousmeasuringFinished! Take the quiz тЖТcheck what stuck

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