Coder logo

Backstage Coder Plugin

Created by Coder

Coder is an open source self hosted platform for cloud development environments. It lets you run secure remote workspaces on your own infrastructure and work from the IDEs your team already uses.

The Coder Backstage plugin brings those workspaces into your portal. Engineers link a Coder account with a token then browse and manage their workspaces without leaving Backstage. The plugin can map catalog entities to workspace templates so the right repo URL travels with each create action. You get a workspace list on the service page plus controls to open or create environments that match the code you are viewing.

Typical use cases are simple. Speed up day one for a new repo. Standardize tools and runtimes across teams. Cut context switching by opening an IDE for the service straight from the catalog. If you need to go beyond the bundled card the plugin exposes hooks and an API layer so you can query Coder from custom pages.

The plugin appears in the Backstage plugin catalog and continues to evolve. Planned items include OAuth support and an Open in Coder action along with examples for the Scaffolder. If you already run Coder this is a small step that gives your Backstage users direct access to ready to code environments.

A screenshot of the Coder plugin showing a Coder workspace with clickable links.

Installation Instructions

These instructions apply to self-hosted Backstage only. To use this plugin on Roadie, visit the docs.

Install the frontend package

Copy
yarn --cwd packages/app add @coder/backstage-plugin-coder

Configure the proxy in app config

Edit app-config.yaml

Copy
proxy:
  endpoints:
    '/coder':
      # Replace with your Coder access URL with a trailing slash
      target: 'https://coder.example.com/'

      changeOrigin: true
      allowedMethods: ['GET'] # More methods will be supported soon
      allowedHeaders: ['Authorization', 'Coder-Session-Token']
      headers:
        X-Custom-Source: backstage

Enable the proxy in the backend

New backend system

Edit packages/backend/src/index.ts

Copy
import { createBackend } from '@backstage/backend-defaults';
import { proxyPlugin } from '@backstage/plugin-proxy-backend';

const backend = createBackend();
backend.add(proxyPlugin());
backend.start();

Old backend system

Create packages/backend/src/plugins/proxy.ts

Copy
import { createRouter } from '@backstage/plugin-proxy';
import type { PluginEnvironment } from '../types';

export default async function createPlugin(env: PluginEnvironment) {
  return await createRouter({
    config: env.config,
    logger: env.logger,
  });
}

Wire it in packages/backend/src/index.ts

Copy
import proxy from './plugins/proxy';

// inside the main bootstrap where apiRouter is defined
apiRouter.use('/proxy', await proxy(env));

Wrap the app with CoderProvider

Edit packages/app/src/App.tsx

Copy
import React from 'react';
import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
import { createApp } from '@backstage/app-defaults';
import { AppRouter } from '@backstage/core-app-api';

// import the provider and types from the Coder plugin
import {
  CoderProvider,
  type CoderAppConfig,
} from '@coder/backstage-plugin-coder';

// your existing routes and app setup above
const app = createApp({
  // existing config
});

const routes = app.getRoutes();

const coderAppConfig: CoderAppConfig = {
  deployment: {
    accessUrl: 'https://coder.example.com',
  },
  workspaces: {
    defaultTemplateName: 'devcontainers',
    defaultMode: 'manual',
    repoUrlParamKeys: ['custom_repo', 'repo_url'],
    params: {
      repo: 'custom',
      region: 'eu-helsinki',
    },
  },
};

export default app.createRoot(
  <CoderProvider appConfig={coderAppConfig}>
    <AlertDisplay />
    <OAuthRequestDialog />
    <AppRouter>{routes}</AppRouter>
  </CoderProvider>,
);

Use your Coder access URL. Keep a trailing slash in app-config.yaml. No slash in the provider accessUrl.

Add the workspaces card to the entity page

Edit packages/app/src/components/catalog/EntityPage.tsx

Copy
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { EntityAboutCard } from '@backstage/plugin-catalog';
import { EntityLayout } from '@backstage/plugin-catalog';
import { EntitySwitch, isComponentType } from '@backstage/plugin-catalog';
import { CoderWorkspacesCard } from '@coder/backstage-plugin-coder';

const overviewContent = (
  <Grid container spacing={3} alignItems="stretch">
    <Grid item md={6}>
      <EntityAboutCard variant="gridItem" />
    </Grid>

    <Grid item md={6} xs={12}>
      <CoderWorkspacesCard readEntityData />
    </Grid>
  </Grid>
);

export const serviceEntityPage = (
  <EntityLayout>
    <EntityLayout.Route path="/" title="Overview">
      {overviewContent}
    </EntityLayout.Route>

    {/* your other routes */}
  </EntityLayout>
);

// export the page you use in your router
export const entityPage = (
  <EntitySwitch>
    <EntitySwitch.Case if={isComponentType('service')}>
      {serviceEntityPage}
    </EntitySwitch.Case>

    {/* your other cases */}
  </EntitySwitch>
);

The card reads the current entity and shows related workspaces. You can also drop the card in any grid section without readEntityData for a general list.

Copy
// General list of all user workspaces
import { CoderWorkspacesCard } from '@coder/backstage-plugin-coder';

function CoderAllWorkspaces() {
  return <CoderWorkspacesCard />;
}

Optional repo settings in catalog info

Add Coder settings to your repo catalog file to control defaults for that entity

catalog-info.yaml in your repo

Copy
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: python-project
spec:
  type: other
  lifecycle: unknown
  owner: pms

  coder:
    templateName: 'devcontainers'
    mode: 'auto'
    params:
      repo: 'custom'
      region: 'us-pittsburgh'

These values override the provider defaults for that entity.

Optional custom usage with hooks

You can import hooks to query the Coder API inside your app components. Wrap with CoderProvider as shown above.

Copy
import React from 'react';
import {
  useCoderWorkspacesConfig,
  useCoderWorkspacesQuery,
  CoderAuthWrapper,
} from '@coder/backstage-plugin-coder';

export function MyWorkspacesSection() {
  const workspacesConfig = useCoderWorkspacesConfig({ readEntityData: true });
  const workspacesQuery = useCoderWorkspacesQuery({
    coderQuery: 'owner:me',
    workspacesConfig,
  });

  return (
    <CoderAuthWrapper type="card">
      <>
        {workspacesQuery.isLoading && <p>Loading</p>}
        {workspacesQuery.isError && <p>Failed to load</p>}
        {workspacesQuery.data?.map(ws => (
          <div key={ws.id}>{ws.name}</div>
        ))}
      </>
    </CoderAuthWrapper>
  );
}

Things to Know

Useful things to know

You can wrap a single page or component with <CoderProvider /> if you only need Coder in a specific part of your app. See the Coder API reference (particularly the section on the CoderProvider component) for more details.

Changelog

The Coder plugin has not seen any significant changes since a year ago.

Set up Backstage in minutes with Roadie