canonical.comis Canonical's Flask-based marketing/site repository.- The app is server-rendered from
templates/andwebapp/, with compiled frontend assets understatic/. - The safest local workflow is
dotrun; CI still also runs directyarn+pythoncommands from the repo root. - Trust the instructions in this file first. Only search the repo if this file is incomplete or you prove one of these instructions is wrong.
README.md- short project overview; confirmsdotrunis the preferred local workflow.webapp/app.py- main Flask app, route registration, sitemap/search/navigation wiring, third-party integrations.webapp/application.py- careers application flow, email sending, Greenhouse/Directory/Calendar integration.webapp/views.py- shared view helpers, JSON asset helpers, knowledge/event index builders.webapp/navigation.py- code that reads and shapes navigation data.webapp/greenhouse.py,webapp/google_calendar.py,webapp/recaptcha.py,webapp/marketo.py,webapp/partners.py- external integrations and feature-specific helpers.webapp/canonical_cla/- Canonical CLA routes/views.templates/- Jinja templates and markdown-backed page content. Most content-only changes happen here.templates/legal/**/*.md- markdown source content rendered by the site.templates/knowledge/**/*- knowledgehub pages.templates/sitemap-index.xml- top-level sitemap index that links to all section sitemaps.templates/sitemap-links.xml,templates/careers/sitemap.xml,templates/partners/sitemap.xml,templates/knowledge/sitemap.xml,templates/data/sitemap.xml- per-section sitemap templates rendered by routes inwebapp/app.py.templates/sitemap_tree.xml- generated (git-ignored) full sitemap; the/sitemap_tree.xmlroute (build_sitemap_treeinwebapp/app.py) regenerates it from thetemplates/tree on aPOSTand serves it onGET.templates/llms.txt- manually maintained LLM-friendly site index (https://llmstxt.org/), committed to git (not generated). Edit this file directly to change what AI crawlers see. Combined withllms.yamlat app startup (webapp/llms.py,build_llms_txt) and served by the/llms.txtroute (llms_txtinwebapp/app.py).templates/llms-full.txt- generated (git-ignored) full Markdown content of every renderable page linked fromllms.txt(webapp/llms.py,build_llms_full_txt). Pre-generated at build time bypython3 webapp/llms.py generate(run in thepack-rockjob of.github/workflows/deploy.yamlbeforerockcraft pack); the/llms-full.txtroute (llms_full_txtinwebapp/app.py) serves it read-only from disk, regenerating on demand only if missing (local dev). Deliberately skips pages served live from Discourse (product docs, tutorials, case studies, events - seeDISCOURSE_PATH_PREFIXESinwebapp/llms.py), since the build environment has no Discourse API access and shouldn't scrape and redistribute that content.static/js/- browser JS/TS entrypoints and React code.static/js/career-explorer/- React/TypeScript careers explorer.static/js/canonical-cla/- React client for the CLA pages.static/js/navigation/andstatic/js/*.js- standalone page behavior modules.
static/sass/- global Sass.build.js- esbuild bundling for the JS/TS entrypoints.scripts/build-modules.sh- copies required browser assets fromnode_modulesintostatic/js/modules/;yarn build-jsdepends on it.navigation.yaml- primary navigation data source.secondary-navigation.yaml- secondary nav/page sidebar data source.redirects.yaml- redirects config.llms.yaml- docs-team-owned config for extra curated links inserted into/llms.txtright after its "Main pages" section (heading+links, each withtitle/url/description), read bywebapp/llms.py. Must be listed inrockcraft.yamlso it ships in the image. Formatting of both this file andtemplates/llms.txtis checked byyarn lint-llms(python3 webapp/llms.py lint), a required PR check (lint-llmsjob in.github/workflows/pr.yaml).tests/- Python unit tests plus JS/Jest and Playwright tests.tests/js/- Jest tests for browser modules.tests/playwright/- E2E tests;tests/playwright/tests/navigation/README.mdexplains navigation coverage.
package.json- authoritative list of yarn scripts.requirements.txt- pinned Python dependencies..github/workflows/pr.yaml- the most important CI definition; use it as the source of truth for lint/test/build expectations..github/workflows/playwright.yaml,forms-test.yaml,percy-pr.yaml- specialized browser/form/visual workflows..github/workflows/sitemap.yaml- on anytemplates/**push tomain, POSTs to the livehttps://canonical.com/sitemap_tree.xmlendpoint withSITEMAP_SECRETto regenerate the git-ignoredtemplates/sitemap_tree.xmlfrom the currenttemplates/tree.Dockerfile- production image build.run- older Docker helper script; works, but README + CI favordotrun.entrypoint- actual Gunicorn/Talisker server command used byyarn serveand container runs.konf/site.yaml- deployment env vars and staging/production overrides; use this to discover real env names.
dotrun- The site serves successfully at
http://localhost:8002. - The first
dotruncommand may take longer than expected because it checks/pulls the dotrun image, creates.venv, and installs dependencies. - For dependency conflicts, it is recommended to run
dotrun cleanfirst.
Dependencies: Python venv in .venv/, Node modules via Yarn.
Copy .env values to .env.local for local overrides (git-ignored).
yarn run build # Build CSS and JS for production
yarn run build-css # SCSS → static/css/
yarn run build-js # TS/JS → static/js/dist/yarn test # All Python + JS tests with coverage
yarn test-python # Python unittest + VCR cassettes
yarn test-js # Jest
yarn test-marketo # Marketo forms test
yarn test-kh # Knowledgehub tests
yarn test-e2e # Playwright end-to-end tests
yarn percy-snapshot # Visual regressionyarn lint-python # flake8 + black --check (line-length 79)
yarn lint-scss # stylelint
yarn format-python # black --line-length 79
yarn format-prettier # prettier
djlint templates/path/to/file.html --lint --profile=jinja # djlint for html/jinja - uses `.djlintrc`| Service | Purpose | Key Env Vars |
|---|---|---|
| Discourse API | Blog, takeovers, docs, tutorials | DISCOURSE_API_KEY, DISCOURSE_API_USERNAME, MAAS_DISCOURSE_API_KEY, MAAS_DISCOURSE_API_USERNAME |
| Careers | Careers related | HARVEST_V3_CLIENT_ID, HARVEST_V3_CLIENT_SECRET, APPLICATION_CRYPTO_SECRET_KEY |
| Greenhouse | Candidate applications | GREENHOUSE_API_KEY |
| Google Calendar | Google Calendar service-account auth | SERVICE_ACCOUNT_EMAIL, SERVICE_ACCOUNT_PRIVATE_KEY |
| Marketo | Marketing / lead gen | MARKETO_API_URL, MARKETO_API_CLIENT, MARKETO_API_SECRET |
| CLA API | Canonical CLA frontend/backend | CANONICAL_CLA_API_URL |
| reCAPTCHA | Bot protection | RECAPTCHA_ENABLED, RECAPTCHA_SITE_KEY, RECAPTCHA_PROJECT_ID, RECAPTCHA_API_KEY, RECAPTCHA_SCORE_THRESHOLD |
| Directory API | Directory lookups | DIRECTORY_API_TOKEN |
| SMTP | Application email sending | SMTP_SERVER, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SENDER_ADDRESS |
| Sitemap | Sitemap generation | SITEMAP_SECRET |
- Start with
.github/workflows/pr.yamlbefore guessing which checks matter. - Always run
dotrunbefore any dotrun build/lint/test command on a fresh clone. - Always reinstall Python dependencies after editing
requirements.txt. - If you change
templates/*.html, rundjlinton the changed file(s). - If you change navigation behavior or menu content, inspect
navigation.yaml,secondary-navigation.yaml,webapp/navigation.py, and the Playwright navigation tests together. - If you change JS/TS entrypoints, remember
yarn build-jsdepends onscripts/build-modules.sh. - If you add or move pages under
templates/, expect thesitemap.yamlworkflow to regenerate the sitemap on merge tomain; update the relevanttemplates/**/sitemap*.xmlsource when a section's URLs change, and runtests/test_sitemap.pyto validate. - Prefer
dotrunfor any task that needs the whole site running. - Trust this file first; only search when the task falls outside these instructions or the instructions prove incomplete.
README.md— project overview, setup, development workflow, and common commandsAGENTS.md— instructions and conventions for AI agents/contributors working in the repository