Hetzner Cloud is a European provider for compute and related infrastructure. You get virtual machines, block storage, private networking, load balancers, and public IPs. Many teams use it for Kubernetes clusters and supporting services. If your stack lives there, you often need a clear view of which app maps to which cloud resource.
The Hetzner Cloud Backstage plugin brings that view into your developer portal. It shows an index page with an overview of your Hetzner projects and resources. It adds a resource card on the entity page so you can see what runs where without leaving Backstage. The backend module syncs servers, volumes, and primary IPs into the catalog to keep things current. This helps during incidents, audits, and onboarding. It reduces the need to grant broad console access. It also makes it easier to trace a service to the exact VM, volume, or IP that backs it.
Gluo uses the plugin in their own portal. They built it to give engineers visibility into their Hetzner setup. As they put it, “At Gluo, we rely on Hetzner Cloud to efficiently host our Kubernetes infrastructure.” See their write up at in their blog about Backstage.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Install the packages
- Add the frontend plugin to your app package
yarn --cwd packages/app add @gluo-nv/backstage-plugin-hetzner
- Add the backend plugin to your backend package
yarn --cwd packages/backend add @gluo-nv/backstage-plugin-hetzner-backend
- Add the catalog backend module to your backend package
yarn --cwd packages/backend add @gluo-nv/backstage-plugin-catalog-backend-module-hetzner
Configure the frontend
- Add basic project info in app config
# app-config.yaml
app:
hetzner:
project:
title: <Hetzner project title>
owner: <Hetzner project owner>
lifecycle: <Hetzner project lifecycle>
- Add the Hetzner page route in your app
Edit packages/app/src/App.tsx
import React from 'react';
import { Route } from 'react-router';
import { FlatRoutes } from '@backstage/core-app-api';
import { HetznerPage } from '@gluo-nv/backstage-plugin-hetzner';
const routes = (
<FlatRoutes>
{/* other routes */}
<Route path="/hetzner" element={<HetznerPage />} />
</FlatRoutes>
);
export default routes;
- Add a sidebar item that links to the page
Edit packages/app/src/components/Root/Root.tsx
import React, { PropsWithChildren } from 'react';
import Cloud from '@material-ui/icons/Cloud';
import { SidebarPage, Sidebar, SidebarGroup, SidebarItem, SidebarDivider } from '@backstage/core-components';
import MenuIcon from '@material-ui/icons/Menu';
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
<SidebarGroup label="Menu" icon={<MenuIcon />}>
{/* other sidebar items */}
<SidebarItem icon={Cloud} to="hetzner" text="Hetzner Cloud" />
<SidebarDivider />
</SidebarGroup>
</Sidebar>
{children}
</SidebarPage>
);
- Add the Hetzner resources card on the entity page
Create packages/app/src/components/catalog/utils.tsx
import { Entity } from '@backstage/catalog-model';
export const isHetznerResource = (entity: Entity): boolean => {
const hetznerData = entity.metadata.annotations?.['hetzner.com/data'];
try {
return hetznerData !== undefined && JSON.parse(hetznerData) !== null;
} catch {
return false;
}
};
Edit packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { EntitySwitch, isKind } from '@backstage/plugin-catalog';
import { EntityAboutCard, EntityCatalogGraphCard } from '@backstage/plugin-catalog';
import { EntityHetznerContent } from '@gluo-nv/backstage-plugin-hetzner';
import { isHetznerResource } from './utils';
// inside your overview content
const overviewContent = (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
<EntityAboutCard variant="gridItem" />
</Grid>
<Grid item md={6} xs={12}>
<EntityCatalogGraphCard variant="gridItem" height={400} />
</Grid>
{/* Hetzner Cloud Plugin */}
<EntitySwitch>
<EntitySwitch.Case
if={entity => isKind('resource')(entity) && isHetznerResource(entity)}
>
<Grid item md={6}>
<EntityHetznerContent />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>
{/* Hetzner Cloud Plugin */}
</Grid>
);
Configure the backend new backend system
- Add the Hetzner token in config
# app-config.yaml
backend:
hetzner:
token: ${HCLOUD_TOKEN}
Set the environment variable in your process manager or shell
export HCLOUD_TOKEN='<your hetzner api token>'
- Register the backend plugin and the catalog backend module
Edit packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// Hetzner backend plugin
backend.add(import('@gluo-nv/backstage-plugin-hetzner-backend'));
// Hetzner catalog backend module
backend.add(import('@gluo-nv/backstage-plugin-catalog-backend-module-hetzner'));
backend.start();
Configure the backend legacy backend system
This plugin ships as modules for the new backend system. A legacy router is not provided. Use the new backend system in your backend package to load these modules. The code above in the new backend section shows the setup.
How catalog entities appear
The catalog backend module discovers Hetzner resources and creates catalog entities for servers volumes and primary IPs. Those entities include Hetzner specific annotations. The resources card on the entity page reads the hetzner.com/data annotation. If you need a quick manual test you can add a resource entity with that annotation. Example only
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
name: demo-hetzner-vm
annotations:
hetzner.com/data: '{"id":12345,"name":"demo","resource_type":"virtual_machine"}'
spec:
type: vm
owner: team-a
After the backend module runs it will keep the catalog in sync with Hetzner. The frontend page at path hetzner shows an overview. The resources card shows details on entities that carry the annotation.
Changelog
This changelog is produced from commits made to the Hetzner Cloud plugin since 6 months ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.
CI
- Add Trivy scan workflow #2 merged 1 week ago
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.