PageSourceSearch

https://backstage.io/assets/js/2c8fa52c.2a205f5c.js

js backstage.io collected 2026-09-24 08:26:51 UTC 16,830 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkbackstage_microsite=self.webpackChunkbackstage_microsite||[]).push([["12240"],{377503(e,n,i){i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>r,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>o});var s=i(643562),t=i(474848),l=i(28453);let a={id:"ci",title:"Setting up CI",sidebar_label:"Setting up CI",description:"Configure continuous integration checks for your Backstage instance."},r,c={},o=[{value:"Why CI matters for Backstage",id:"why-ci-matters-for-backstage",level:2},{value:"What to check",id:"what-to-check",level:2},{value:"Lint",id:"lint",level:3},{value:"Type checking",id:"type-checking",level:3},{value:"Tests",id:"tests",level:3},{value:"Deprecated API usage",id:"deprecated-api-usage",level:3},{value:"Build",id:"build",level:3},{value:"Configuration validation",id:"configuration-validation",level:3},{value:"Docker build (optional)",id:"docker-build-optional",level:3},{value:"GitHub Actions",id:"github-actions",level:2},{value:"Node.js and Yarn setup",id:"nodejs-and-yarn-setup",level:3},{value:"CI steps",id:"ci-steps",level:3},{value:"Customizing the workflow",id:"customizing-the-workflow",level:3},{value:"Other CI systems",id:"other-ci-systems",level:2},{value:"GitLab CI",id:"gitlab-ci",level:3},{value:"Jenkins",id:"jenkins",level:3},{value:"Azure DevOps",id:"azure-devops",level:3},{value:"Key differences from GitHub Actions",id:"key-differences-from-github-actions",level:3},{value:"Environment variables",id:"environment-variables",level:2}];function d(e){let n={a:"a",blockquote:"blockquote",code:"code",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,l.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.p,{children:"Audience: Developers and Admins"}),"\n",(0,t.jsx)(n.h2,{id:"why-ci-matters-for-backstage",children:"Why CI matters for Backstage"}),"\n",(0,t.jsx)(n.p,{children:"A Backstage instance is a living project. As you add plugins, customize\nconfiguration, and update dependencies, things can break in subtle ways: a\nTypeScript error in one package, a config typo that prevents the backend\nfrom starting, or a Docker image that no longer builds. Continuous\nIntegration (CI) catches these problems on every pull request, before they\nreach production."}),"\n",(0,t.jsx)(n.p,{children:"A good CI pipeline for Backstage verifies that:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Code 
1compiles and passes lint checks"}),"\n",(0,t.jsx)(n.li,{children:"Tests pass across all packages in the monorepo"}),"\n",(0,t.jsx)(n.li,{children:"Configuration files are valid"}),"\n",(0,t.jsx)(n.li,{children:"The deployment artifact (typically a Docker image) builds successfully"}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"what-to-check",children:"What to check"}),"\n",(0,t.jsx)(n.p,{children:"These checks apply regardless of which CI system you use. Most map to\ncommands provided by the Backstage CLI."}),"\n",(0,t.jsx)(n.h3,{id:"lint",children:"Lint"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-shell",children:"yarn backstage-cli repo lint\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Runs ESLint across all packages in the monorepo. This catches code quality\nissues, unused imports, and style violations. See\n",(0,t.jsx)(n.a,{href:"/docs/tooling/cli/module-lint#repo-lint",children:"repo lint"})," for available options."]}),"\n",(0,t.jsx)(n.h3,{id:"type-checking",children:"Type checking"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-shell",children:"yarn tsc:full\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Runs the TypeScript compiler with ",(0,t.jsx)(n.code,{children:"--skipLibCheck false"})," and\n",(0,t.jsx)(n.code,{children:"--incremental false"}),", performing a complete type check across the entire\nproject. This is stricter than the default ",(0,t.jsx)(n.code,{children:"yarn tsc"})," and catches type\nerrors that incremental builds might miss."]}),"\n",(0,t.jsx)(n.h3,{id:"tests",children:"Tests"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-shell",children:"yarn backstage-cli repo test\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Runs the test suite for all packages. The Backstage CLI automatically\ndetects which test runner to use and handles monorepo-specific\nconfiguration. See\n",(0,t.jsx)(n.a,{href:"/docs/tooling/cli/module-test#repo-test",children:"repo test"})," for available\noptions."]}),"\n",(0,t.jsx)(n.h3,{id:"deprecated-api-usage",children:"Deprecated API usage"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-shell",children:"yarn backstage-cli repo list-deprecations\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Scans your code for usage of deprecated Backstage APIs. This is especially\nuseful when preparing for version upgrades, but running it in CI ensures\nnew code doesn't introduce deprecated patterns. See\n",(0,t.jsx)(n.a,{href:"/docs/tooling/cli/module-maintenance#repo-list-deprecations",children:"repo list-deprecations"}),"\nfor available options."]}),"\n",(0,t.jsx)(n.h3,{id:"build",children:"Build"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-shell",children:"yarn build:all\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Builds all packages in the monorepo, including the backend bundle that the\nDockerfile needs. Running the build in CI catches compilation errors,\nmissing dependencies, and broken imports that type checking alone might\nnot surface. See\n",(0,t.jsx)(n.a,{href:"/docs/tooling/cli/module-build#repo-build",children:"repo build"})," for available\noptions."]}),"\n",(0,t.jsx)(n.h3,{id:"configuration-validation",children:"Configuration validation"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-shell",children:"yarn backstage-cli config:check --lax --strict \\\n  --config app-config.yaml \\\n  --config app-config.production.yaml\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Validates your configuration files against the configuration schema. The\n",(0,t.jsx)(n.code,{children:"--lax"})," flag allows environment variables to remain unresolved (useful in\nCI where production secrets aren't available), while ",(0,t.jsx)(n.code,{children:"--strict"})," ensures\nthe config structure matches the schema. Passing multiple ",(0,t.jsx)(n.code,{children:"--config"}),"\nflags lets you validate your production configuration alongside the\ndefault. See\n",(0,t.jsx)(n.a,{href:"/docs/tooling/cli/module-config#configcheck",children:"config:check"})," for available\noptions."]}),"\n",(0,t.jsx)(n.h3,{id:"docker-build-optional",children:"Docker build (optional)"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-shell",children:"yarn build-image\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Verifies that the Docker image builds successfully. This script runs\n",(0,t.jsx)(n.code,{children:"docker build"})," with the correct build context and Dockerfile path. It\ncatches issues like incompatible dependencies that only surface at\npackaging time. This step expects pre-built backend bundles from\n",(0,t.jsx)(n.code,{children:"yarn build:all"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"This step is optional. Docker builds can be slow, so some teams prefer to\nrun them only on merges to the default branch or on a scheduled basis\nrather than on every pull request."}),"\n",(0,t.jsx)(n.h2,{id:"github-actions",children:"GitHub Actions"}),"\n",(0,t.jsxs)(n.p,{children:["If you created your Backstage instance with ",(0,t.jsx)(n.code,{children:"create-app"}),", a GitHub Actions\nworkflow is included at ",(0,t.jsx)(n.code,{children:".github/workflows/ci.yml"}),". It runs all of the\nchecks above on every pull request."]}),"\n",(0,t.jsx)(n.p,{children:"Here's what the workflow does, step by step:"}),"\n",(0,t.jsx)(n.h3,{id:"nodejs-and-yarn-setup",children:"Node.js and Yarn setup"}),"\n",(0,t.jsxs)(n.p,{children:["The workflow installs Node.js 24.x and caches both ",(0,t.jsx)(n.code,{children:"node_modules"})," and the\nglobal Yarn cache. On subsequent runs, ",(0,t.jsx)(n.code,{children:"yarn install --immutable"})," finishes\nin seconds when the lockfile hasn't changed."]}),"\n",(0,t.jsx)(n.h3,{id:"ci-steps",children:"CI steps"}),"\n",(0,t.jsx)(n.p,{children:"The pipeline runs these steps in order:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Lint"})," -- checks code quality across all packages"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Type checking"})," -- full TypeScript compilation"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Deprecations"})," -- flags deprecated API usage"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Tests"})," -- runs the full test suite"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Build"})," -- builds all packages (",(0,t.jsx)(n.code,{children:"yarn build:all"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Config check"})," -- validates default and production configuration together"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Docker build"})," -- verifies the container image builds"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Tests run before the full build to give faster feedback on the most common\nfailure mode. Build runs before the Docker build because the Dockerfile\nexpects pre-built backend bundles."}),"\n",(0,t.jsx)(n.h3,{id:"customizing-the-workflow",children:"Customizing the workflow"}),"\n",(0,t.jsx)(n.p,{children:"Common modifications:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Add a matrix strategy"})," to test on multiple Node.js versions (the\ntemplate supports Node.js 22 and 24)."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Add a deploy step"})," that pushes the Docker image to a registry when\nmerging to your default branch."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Add end-to-e
1nd tests"})," using the included Playwright configuration\n(",(0,t.jsx)(n.code,{children:"yarn test:e2e"}),")."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"other-ci-systems",children:"Other CI systems"}),"\n",(0,t.jsx)(n.p,{children:"The same checks work in any CI system -- only the pipeline syntax changes."}),"\n",(0,t.jsx)(n.h3,{id:"gitlab-ci",children:"GitLab CI"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"stages:\n  - validate\n  - build\n  - test\n\ndefault:\n  cache:\n    key:\n      files:\n        - yarn.lock\n    paths:\n      - node_modules/\n      - .yarn/cache/\n\nvariables:\n  CI: 'true'\n  NODE_OPTIONS: '--max-old-space-size=8192'\n\nlint:\n  stage: validate\n  script:\n    - yarn install --immutable\n    - yarn backstage-cli repo lint\n\ntype-check:\n  stage: validate\n  script:\n    - yarn install --immutable\n    - yarn tsc:full\n\nbuild:\n  stage: build\n  script:\n    - yarn install --immutable\n    - yarn build:all\n\ntest:\n  stage: test\n  script:\n    - yarn install --immutable\n    - yarn backstage-cli repo test\n"})}),"\n",(0,t.jsx)(n.h3,{id:"jenkins",children:"Jenkins"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-groovy",children:"pipeline {\n  agent {\n    docker {\n      image 'node:24-slim'\n    }\n  }\n  environment {\n    CI = 'true'\n    NODE_OPTIONS = '--max-old-space-size=8192'\n  }\n  stages {\n    stage('Install') {\n      steps {\n        sh 'yarn install --immutable'\n      }\n    }\n    stage('Lint') {\n      steps {\n        sh 'yarn backstage-cli repo lint'\n      }\n    }\n    stage('Type check') {\n      steps {\n        sh 'yarn tsc:full'\n      }\n    }\n    stage('Build') {\n      steps {\n        sh 'yarn build:all'\n      }\n    }\n    stage('Test') {\n      steps {\n        sh 'yarn backstage-cli repo test'\n      }\n    }\n    stage('Config check') {\n      steps {\n        sh 'yarn backstage-cli config:check --lax --strict --config app-config.yaml --config app-config.production.yaml'\n      }\n    }\n  }\n}\n"})}),"\n",(0,t.jsx)(n.h3,{id:"azure-devops",children:"Azure DevOps"}),"\n",(0,t.jsxs)(n.blockquote,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Note:"})," YAML ",(0,t.jsx)(n.code,{children:"pr:"})," triggers only work when your repo is hosted on\nGitHub or Bitbucket Cloud. If you use Azure Repos Git, configure a\n",(0,t.jsx)(n.a,{href:"https://learn.microsoft.com/en-us/azure/devops/repos/git/branch-policies#build-validation",children:"branch policy for build validation"}),"\nto run this pipeline on pull requests."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"trigger:\n  - main\n\npool:\n  vmImage: 'ubuntu-latest'\n\nvariables:\n  CI: 'true'\n  NODE_OPTIONS: '--max-old-space-size=8192'\n\nsteps:\n  - task: NodeTool@0\n    inputs:\n      versionSpec: '24.x'\n    displayName: use node.js\n\n  - script: yarn install --immutable\n    displayName: yarn install\n\n  - script: yarn backstage-cli repo lint\n    displayName: lint\n\n  - script: yarn tsc:full\n    displayName: type checking\n\n  - script: yarn backstage-cli repo list-deprecations\n    displayName: deprecations\n\n  - script: yarn build:all\n    displayName: build\n\n  - script: yarn backstage-cli repo test\n    displayName: tests\n\n  - script: yarn backstage-cli config:check --lax --strict --config app-config.yaml --config app-config.production.yaml\n    displayName: config check\n"})}),"\n",(0,t.jsx)(n.h3,{id:"key-differences-from-github-actions",children:"Key differences from GitHub Actions"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Caching"}),": GitHub Actions has built-in cache actions. Other systems\nrequire you to configure cache paths and keys manually."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Docker-in-Docker"}),": Some CI systems require extra configuration to run\n",(0,t.jsx)(n.code,{children:"docker build"})," inside a pipeline. Check your platform's documentation\nfor Docker support."]}
1),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Environment variables"}),": Set ",(0,t.jsx)(n.code,{children:"CI=true"})," and\n",(0,t.jsx)(n.code,{children:"NODE_OPTIONS=--max-old-space-size=8192"})," in your pipeline environment.\nThe ",(0,t.jsx)(n.code,{children:"CI"})," variable ensures deterministic behavior in tools like Jest, and\nthe memory limit is explained below."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"environment-variables",children:"Environment variables"}),"\n",(0,t.jsx)(n.p,{children:"Two environment variables are set in the generated workflow:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"CI=true"})})," -- Enables CI-specific behavior in tools like Jest (for\nexample, running all tests instead of only changed ones) and prevents\ninteractive prompts."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"NODE_OPTIONS=--max-old-space-size=8192"})})," -- Increases the Node.js\nheap memory limit to 8 GB. TypeScript compilation and bundling across a\nmonorepo can exceed the default memory limit, especially as you add\nplugins. This setting prevents out-of-memory crashes during\n",(0,t.jsx)(n.code,{children:"yarn tsc:full"})," and ",(0,t.jsx)(n.code,{children:"yarn build:all"}),"."]}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,l.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},28453(e,n,i){i.d(n,{R:()=>a,x:()=>r});var s=i(296540);let t={},l=s.createContext(t);function a(e){let n=s.useContext(l);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),s.createElement(l.Provider,{value:n},e.children)}},643562(e){e.exports=JSON.parse('{"id":"getting-started/ci","title":"Setting up CI","description":"Configure continuous integration checks for your Backstage instance.","source":"@site/versioned_docs/version-stable/getting-started/ci.md","sourceDirName":"getting-started","slug":"/getting-started/ci","permalink":"/docs/getting-started/ci","draft":false,"unlisted":false,"editUrl":"https://github.com/backstage/backstage/edit/master/docs/getting-started/ci.md","tags":[],"version":"stable","frontMatter":{"id":"ci","title":"Setting up CI","sidebar_label":"Setting up CI","description":"Configure continuous integration checks for your Backstage instance."},"sidebar":"docs","previous":{"title":"Backstage homepage - Setup and Customization","permalink":"/docs/getting-started/homepage"},"next":{"title":"Deploying Backstage","permalink":"/docs/deploying-backstage/generated-index"}}')}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.