Project: Backstage (App Definition and Development / Application Definition & Image Build)
File: how-to-guides.md

id: how-to-guides title: TechDocs How-To guides sidebar_label: How-To guides description: TechDocs How-To guides related to TechDocs The main difference between TechDocs Basic and Recommended deployment approach is where the docs are generated and stored. In Basic or the out-of-the-box setup, docs are generated and stored at the server running your Backstage instance. But the recommended setup is to generate docs on CI/CD and store the generated sites to an external storage (e.g. AWS S3 or GCS). TechDocs in your Backstage instance should turn into read-only mode. Read more details and the benefits in the . Here are the steps needed to switch from the Basic to Recommended setup - Choose a cloud storage provider like AWS, Google Cloud or Microsoft Azure. Follow the detailed instructions for in TechDocs. Start publishing your TechDocs sites from the CI/CD workflow of each repository containing the source markdown files. Read the detailed instructions for . In your Backstage instance's `app-config.yaml`, set `techdocs.builder` from `'local'` to `'external'`. By doing this, TechDocs will not try to generate docs. Look at for reference. If TechDocs is configured to generate docs, it will first download source files based on the value of the `backstage.io/techdocs-ref` annotation defined in the Entity's `catalog-info.yaml` file. This is also called the step. We strongly recommend that the `backstage.io/techdocs-ref` annotation in each documented catalog entity's `catalog-info.yaml` be set to `dir:.` in almost all situations. This is because TechDocs is aligned with the "docs like code" philosophy, whereby documentation should be authored and managed alongside the source code of the underlying software itself. When you see `dir:.`, you can translate it to mean: That the documentation source code lives in the same location as the `catalog-info.yaml` file. That, in particular, the `mkdocs.yml` file is a sibling of `catalog-info.yaml` (meaning, it is in the same directory) And that all of the source content of the documentation would be available if one were to download the directory containing those two files (as well as all sub-directories). The directory tree of the entity would look something like this: ``` catalog-info.yaml mkdocs.yml docs index.md ``` If, for example, you wanted to keep a lean root directory, you could place your `mkdocs.yml` file in a subdirectory and update the `backstage.io/techdocs-ref` annotation value accordingly, e.g. to `dir:./sub-folder`: ``` catalog-info.yaml sub-folder mkdocs.yml docs index.md ``` In rare situations where your TechDocs source content is managed and stored in a location completely separate from your `catalog-info.yaml`, you can instead specify a URL location reference, the exact value of which will vary based on the source code hosting provider. Notice that instead of the `dir:` prefix, the `url:` prefix is used instead. For example: GitHub: `url:https://githubhost.com/org/repo/tree/<branch_name>` GitLab: `url:https://gitlabhost.com/org/repo/tree/<branch_name>` Bitbucket: `url:https://bitbuckethost.com/project/repo/src/<branch_name>` Azure: `url:https://azurehost.com/organization/project/_git/repository` Note, just as it's possible to specify a subdirectory with the `dir:` prefix, you can also provide a path to a non-root directory inside the repository which contains the `mkdocs.yml` file and `docs/` directory. e.g. `url:https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/examples/documented-component` URL Reader uses the source code hosting provider to download a zip or tarball of the repository. The archive does not have any git history attached to it. Also it is a compressed file. Hence the file size is significantly smaller than how much data git clone has to transfer. TechDocs uses a composability pattern similar to the Search and Catalog plugins in

While a default table experience, similar to the one provided by the Catalog plugin, is made available for ease-of-use, it's possible for you to provide a completely custom experience, tailored to the needs of your organization. For example, TechDocs comes with an alternative grid based layout (`<EntityListDocsGrid>`) and panel layout (`TechDocsCustomHome`). This is done in your `app` package. By default, you might see something like this in your `App.tsx`: ```tsx const AppRoutes = () => { <FlatRoutes> <Route path="/docs" element={<TechDocsIndexPage />}> <DefaultTechDocsHome /> </Route> </FlatRoutes>; }; ``` You can easily customize the TechDocs home page using TechDocs panel layout (`<TechDocsCustomHome />`). Modify your `App.tsx` as follows: ```tsx import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; //... const techDocsTabsConfig = [ { label: 'Recommended Documentation', panels: [ { title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', filterPredicate: entity => entity?.metadata?.tags?.includes('recommended') ?? false, }, ], }, ]; const AppRoutes = () => { <FlatRoutes> <Route path="/docs" element={<TechDocsCustomHome tabsConfig={techDocsTabsConfig} />} /> </FlatRoutes>; }; ``` But you can replace `<DefaultTechDocsHome />` with any React component, which will be rendered in its place. Most likely, you would want to create and maintain such a component in a new directory at `packages/app/src/components/techdocs`, and import and use it in `App.tsx`: For example, you can define the following Custom home page component: ```tsx import React from 'react'; import { Content } from '@backstage/core-components'; import { CatalogFilterLayout, EntityOwnerPicker, EntityTagPicker, UserListPicker, EntityListProvider, } from '@backstage/plugin-catalog-react'; import { TechDocsPageWrapper, TechDocsPicker, } from '@backstage/plugin-techdocs'; import { Entity } from '@backstage/catalog-model'; import { EntityListDocsGrid } from '@backstage/plugin-techdocs'; export type CustomTechDocsHomeProps = { groups?: Array<{ title: React.ReactNode; filterPredicate: ((entity: Entity) => boolean) | string; }>; }; export const CustomTechDocsHome = ({ groups }: CustomTechDocsHomeProps) => { return ( <TechDocsPageWrapper> <Content> <EntityListProvider> <CatalogFilterLayout> <CatalogFilterLayout.Filters> <TechDocsPicker /> <UserListPicker initialFilter="all" /> <EntityOwnerPicker /> <EntityTagPicker /> </CatalogFilterLayout.Filters> <CatalogFilterLayout.Content> <EntityListDocsGrid groups={groups} /> </CatalogFilterLayout.Content> </CatalogFilterLayout> </EntityListProvider> </Content> </TechDocsPageWrapper> ); }; ``` Then you can add the following to your `App.tsx`: ```tsx import { CustomTechDocsHome } from './components/techdocs/CustomTechDocsHome'; // ... const AppRoutes = () => { <FlatRoutes> <Route path="/docs" element={<TechDocsIndexPage />}> <CustomTechDocsHome groups={[ { title: 'Recommended Documentation', filterPredicate: entity => entity?.metadata?.tags?.includes('recommended') ?? false, }, { title: 'My Docs', filterPredicate: 'ownedByUser', }, ]} /> </Route> </FlatRoutes>; }; ``` Similar to how it is possible to customize the TechDocs Home, it is also possible to customize the TechDocs Reader Page. It is done in your `app` package. By default, you might see something like this in your `App.tsx`: ```tsx const AppRoutes = () => { <Route path="/docs/:namespace/:kind/:name/*" element={<TechDocsReaderPage />}> {techDocsPage} </Route>; }; ``` The `techDocsPage` is a default techdocs reader page which lives in `packages/app/src/components/techdocs`. It includes the following without you having to set anything up. ```tsx <Page themeId="documentation"> <TechDocsReaderPageHeader /> <TechDocsReaderPageSubheader /> <TechDocsReaderPageContent /> </Page> ``` If you would like to compose your own `techDocsPage`, you can do so by replacing the children of TechDocsPage with something else. Maybe you are just interested in replacing the Header: ```tsx <Page themeId="documentation"> <Header type="documentation" title="Custom Header" /> <TechDocsReaderPageContent /> </Page> ``` Or maybe you want to disable the in-context search ```tsx <Page themeId="documentation"> <Header type="documentation" title="Custom Header" /> <TechDocsReaderPageContent withSearch={false} /> </Page> ``` Or maybe you want to replace the entire TechDocs

```tsx <Page themeId="documentation"> <Header type="documentation" title="Custom Header" /> <Content data-testid="techdocs-content"> <p>my own content</p> </Content> </Page> ``` This guide only applies to the "recommended" TechDocs deployment method (where an external storage provider and external CI/CD is used). If you use the "basic" or "out-of-the-box" setup, you can stop here! No action needed. For the purposes of this guide, TechDocs Beta version is defined as: TechDocs Plugin: At least `v0.11.0` TechDocs Backend Plugin: At least `v0.10.0` TechDocs CLI: At least `v0.7.0` The beta version of TechDocs made a breaking change to the way TechDocs content was accessed and stored, allowing pages to be accessed with case-insensitive entity triplet paths (e.g. `/docs/namespace/kind/name` whereas in prior versions, they could only be accessed at `/docs/namespace/Kind/name`). In order to enable this change, documentation has to be stored in an external storage provider using an object key whose entity triplet is lower-cased. New installations of TechDocs since the beta version will work fine with no action, but for those who were running TechDocs prior to this version, a migration will need to be performed so that all existing content in your storage bucket matches this lower-case entity triplet expectation. Ensure you have the right permissions on your storage provider: In order to migrate files in your storage provider, the `techdocs-cli` needs to be able to read/copy/rename/move/delete files. The exact instructions vary by storage provider, but check the page for details. Run a non-destructive migration of files: Ensure you have the latest version of `techdocs-cli` installed. Then run the following command, using the details relevant for your provider / configuration. This will copy all files from, e.g. `namespace/Kind/name/index.html` to `namespace/kind/name/index.html`, without removing the original files. ```sh techdocs-cli migrate --publisher-type <awsS3|googleGcs|azureBlobStorage> --storage-name <bucket/container name> --verbose ``` Deploy the updated versions of the TechDocs plugins: Once the migration above has been run, you can deploy the beta versions of the TechDocs backend and frontend plugins to your Backstage instance. Verify that your TechDocs sites are still loading/accessible: Try accessing a TechDocs site using different entity-triplet case variants, e.g. `/docs/namespace/KIND/name` or `/docs/namespace/kind/name`. Your TechDocs site should load regardless of the URL path casing you use. Clean up the old objects from storage: Once you've verified that your TechDocs site is accessible, you can clean up your storage bucket by re-running the `migrate` command on the TechDocs CLI, but with an additional `removeOriginal` flag passed: ```sh techdocs-cli migrate --publisher-type <awsS3|googleGcs|azureBlobStorage> --storage-name <bucket/container name> --removeOriginal --verbose ``` Update your CI/CD pipelines to use the beta version of the TechDocs CLI: Finally, you can update all of your CI/CD pipelines to use at least v0.x.y of the TechDocs CLI, ensuring that all sites are published to the new, lower-cased entity triplet paths going forward. If you encounter problems running this migration, please [report the issue][beta-migrate-bug]. You can temporarily revert to pre-beta storage expectations with a configuration change: ```yaml techdocs: legacyUseCaseSensitiveTripletPaths: true ``` The TechDocs plugin provides implementations of two primary APIs by default: the , which is responsible for talking to TechDocs storage to fetch files to render, and , which is responsible for talking to techdocs-backend. There may be occasions where you need to implement these two APIs yourself, to customize them to your own needs. The purpose of this guide is to walk you through how to do that in two steps. Implement the `TechDocsStorageApi` and `TechDocsApi` interfaces according to your

```typescript export class TechDocsCustomStorageApi implements TechDocsStorageApi { // your implementation } export class TechDocsCustomApiClient implements TechDocsApi { // your implementation } ``` Override the API refs `techdocsStorageApiRef` and `techdocsApiRef` with your new implemented APIs in the `App.tsx` using `ApiFactories`. . ```typescript const app = createApp({ apis: [ // TechDocsStorageApi createApiFactory({ api: techdocsStorageApiRef, deps: { discoveryApi: discoveryApiRef, configApi: configApiRef }, factory({ discoveryApi, configApi }) { return new TechDocsCustomStorageApi({ discoveryApi, configApi }); }, }), // TechDocsApi createApiFactory({ api: techdocsApiRef, deps: { discoveryApi: discoveryApiRef }, factory({ discoveryApi }) { return new TechDocsCustomApiClient({ discoveryApi }); }, }), ], }); ``` in Backstage is a tool that can help your users to create new components out of already configured templates. It comes with a set of default templates to use, but you can also . If you have your own templates set up, we highly recommend that you include the required setup for TechDocs in those templates. When creating a new component, your users will then get a TechDocs site up and running automatically, ready for them to start writing technical documentation. The purpose of this how-to guide is to walk you through how to add the required configuration and some default markdown files to your new template. You can use the as a reference when walking through the steps. Prerequisites: An existing software template including a `template.yaml` together with a skeleton folder including at least a `catalog-info.yaml`. Update your component's entity description by adding the following lines to the `catalog-info.yaml` in your skeleton folder. ```yaml annotations: backstage.io/techdocs-ref: dir:. ``` The is used by TechDocs to download the documentation source files for generating an entity's TechDocs site. Create an `mkdocs.yml` file in the root of your skeleton folder with the following content: ```yaml sitename: ${{values.componentid}} site_description: ${{values.description}} nav: Introduction: index.md plugins: techdocs-core ``` Create a `/docs` folder in the skeleton folder with at least an `index.md` file in it. The `docs/index.md` can for example have the following content: ```markdown ${{ values.description }} Start writing your documentation by adding more markdown (.md) files to this folder (/docs) or replace the content in this file. ``` :::note Note The values of `sitename`, `componentid` and `site_description` depends on how you have configured your `template.yaml`. ::: Done! You now have support for TechDocs in your own software template! If your Backstage instance does not have internet access, the generation will fail. TechDocs tries to download the Roboto font from Google. You can disable it by adding the following lines to mkdocs.yaml: ```yaml theme: name: material font: false ``` :::note Note The addition `name: material` is necessary. Otherwise it will not work ::: TechDocs uses the library to sanitize HTML and prevent XSS attacks. It's possible to allow some iframes based on a list of allowed hosts. To do this, add the allowed hosts in the `techdocs.sanitizer.allowedIframeHosts` configuration of your `app-config.yaml`. For example: ```yaml techdocs: sanitizer: allowedIframeHosts: drive.google.com ``` This way, all iframes where the host in the src attribute is in the `sanitizer.allowedIframeHosts` list will be displayed. PlantUML allows you to create diagrams from plain text language. Each diagram description begins with the keyword - (@startXYZ and @endXYZ, depending on the kind of diagram). For UML Diagrams, Keywords @startuml & @enduml should be used. Further details for all types of diagrams can be found at

Here, the markdown file itself contains the diagram description. ````md ```plantuml @startuml title Login Sequence ComponentA->ComponentB: Login Request note right of ComponentB: ComponentB logs message ComponentB->ComponentA: Login Response @enduml ``` ```` Here, the markdown file refers to another file (`.puml` or `.pu`) which contains the diagram description. ````md ```plantuml !include umldiagram.puml ``` ```` Note: To refer external diagram files, we need to include the diagrams directory in the path. Please refer for details. To add `Mermaid` support in TechDocs, you can use that creates diagrams from Textual descriptions. It is a single rendering gateway for all popular diagrams-as-a-code tools. It supports an enormous number of diagram types. Create and Publish Docker image: Create the Docker image from the following `Dockerfile` and publish it to DockerHub. ```docker FROM python:3.10-alpine RUN apk update && apk --no-cache add gcc musl-dev openjdk11-jdk curl graphviz ttf-dejavu fontconfig RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==1.2.0 RUN pip install mkdocs-kroki-plugin ENTRYPOINT [ "mkdocs" ] ``` Create a repository in your DockerHub and run the below command in the same folder where your `Dockerfile` is present: ```shell docker build . -t dockerHub_Username/repositoryName:tagName ``` Once the docker image is ready, push it to DockerHub. Update app-config.yaml: So that when your app generates TechDocs, it will pull your docker image from DockerHub. ```python techdocs: builder: 'local' # Alternatives - 'external' generator: runIn: 'docker' # Alternatives - 'local' dockerImage: dockerHub_Username/repositoryName:tagName pullImage: true publisher: type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives. ``` Add the `kroki` plugin in `mkdocs.yml`: ```yml plugins: techdocs-core kroki ``` :::note Note You will very likely want to set a `kroki` `ServerURL` configuration in your `mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If you have sensitive information in your organization's diagrams, you should set up a and use it instead. Check out for more plugin configuration details. ::: Add mermaid code into TechDocs: ````md ```kroki-mermaid sequenceDiagram GitLab->>Kroki: Request rendering Kroki->>Mermaid: Request rendering Mermaid-->>Kroki: Image Kroki-->>GitLab: Image ``` ```` Done! Now you have a support of the following diagrams along with mermaid: `PlantUML` `BlockDiag` `BPMN` `ByteField` `SeqDiag` `ActDiag` `NwDiag` `PacketDiag` `RackDiag` `C4 with PlantUML` `Ditaa` `Erd` `Excalidraw` `GraphViz` `Nomnoml` `Pikchr` `Svgbob` `UMlet` `Vega` `Vega-Lite` `WaveDrom` One limitation of the is that the experience for users requires modifying their CI/CD process to publish their TechDocs. For some users, this may be unnecessary, and provides a barrier to entry for onboarding users to Backstage. However, a purely local TechDocs build restricts TechDocs creators to using the tooling provided in Backstage, as well as the plugins and features provided in the Backstage-included `mkdocs` installation. To accommodate both of these use-cases, users can implement a custom with logic to encode which TechDocs should be built locally, and which will be built externally. To achieve this hybrid build model: In your Backstage instance's `app-config.yaml`, set `techdocs.builder` to `'local'`. This ensures that Backstage will build docs for users who want the 'out-of-the-box' experience. Configure external storage of TechDocs as normal for a production deployment. This allows Backstage to publish documentation to your storage, as well as allowing other users to publish documentation from their CI/CD pipelines. Create a custom build strategy, that implements the `DocsBuildStrategy` interface, and which implements your custom logic for determining whether to build docs for a given

For example, to only build docs when an entity has the `company.com/techdocs-builder` annotation set to `'local'`: ```typescript export class AnnotationBasedBuildStrategy { private readonly config: Config; constructor(config: Config) { this.config = config; } async shouldBuild(_: Entity): Promise<boolean> { return ( this.entity.metadata?.annotations?.['company.com/techdocs-builder'] === 'local' ); } } ``` Pass an instance of this Build Strategy as the `docsBuildStrategy` parameter of the TechDocs backend `createRouter` method. Users should now be able to choose to have their documentation built and published by the TechDocs backend by adding the `company.com/techdocs-builder` annotation to their entity. If the value of this annotation is `'local'`, the TechDocs backend will build and publish the documentation for them. If the value of the `company.com/techdocs-builder` annotation is anything other than `'local'`, the user is responsible for publishing documentation to the appropriate location in the TechDocs external storage. To setup a hybrid build strategy using the New Backend System you'll follow the same steps as above but for Step 4 you will need to do the following: ```ts title="packages/backend/src/index.ts" const backend = createBackend(); import { createBackendModule } from '@backstage/backend-plugin-api'; import { DocsBuildStrategy, techdocsBuildsExtensionPoint, } from '@backstage/plugin-techdocs-node'; const techdocsCustomBuildStrategy = createBackendModule({ pluginId: 'techdocs', moduleId: 'customBuildStrategy', register(env) { env.registerInit({ deps: { techdocs: techdocsBuildsExtensionPoint, }, async init({ techdocs }) { const docsBuildStrategy: DocsBuildStrategy = { shouldBuild: async params => params.entity.metadata?.annotations?.[ 'demo.backstage.io/techdocs-builder' ] === 'local', }; techdocs.setBuildStrategy(docsBuildStrategy); }, }); }, }); // Other plugins... / highlight-add-start / backend.add(import('@backstage/plugin-techdocs-backend/alpha')); backend.add(techdocsCustomBuildStrategy()); / highlight-add-end / backend.start(); ``` :::note Note You may need to add the `@backstage/plugin-techdocs-node` package to your backend `package.json` if it's not been imported already. ::: The default plugin provides a set of plugins that can be viewed as the minimum required plugins to enable TechDocs. Your organization might have needs beyond the core set though, here is the recommended way to enable other plugins. If you generate the HTML files in CI using `@techdocs/cli`, you need to install the desired mkdocs plugin in the runtime where the cli is being executed. This might be e.g. a docker image or a Jenkins node. Use the `--no-docker` flag with the cli to pick up the plugin you just installed. Create a new Docker image that extends , roughly: ```Dockerfile FROM spotify/techdocs:<version> pip install <thepluginyou_want> ... ``` Then publish the image and use it in your config under the `techdocs.generator.dockerImage` . To use the plugin, it has to be listed in the `mkdocs.yaml` file. You can either add the plugin to your applicable files, or specify defaults. To make a mkdocs plugin available for all your TechDocs components you can either list it in the `techdocs.generator.mkdocs.defaultPlugins` , or use the `--defaultPlugin` depending on your setup. In systems where you might have multiple entities for example a System with a Website and an API, when served from a Monorepo you might want to keep the TechDocs in one location in the repository. In this case you can add the `backstage.io/techdocs-entity` annotation and point to the owners `entityRef` and use its TechDocs. This allows the Subcomponents to read the parents docs, filling the TechDocs link on the `AboutCard` element and the Techdocs tab ```yaml apiVersion: backstage.io/v1alpha1 kind: System metadata: name: example namespace: default title: Example description: This is the parent entity annotations: backstage.io/techdocs-ref: dir:. apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: example-platfrom title: Example Application Platform namespace: default description: This is the child entity annotations: backstage.io/techdocs-entity: system:default/example ```