Dev Containers logo

Backstage Dev Containers Plugin

Created by Coder

Dev Containers let you define a repeatable dev setup inside your repo using a simple config. Open the project in VS Code. The tools and services you need are ready. New contributors can start coding without chasing setup steps or system quirks.

This plugin brings that flow into Backstage. On an entity page it detects repos that use Dev Containers. It shows a direct link that opens the repo in VS Code with the right container config. The React plugin gives you a small provider and a hook. Your components can read whether a repo supports Dev Containers. They can render the launch link when it is available.

Add the companion backend plugin if you want more automation. It scans your source control for devcontainer config files. It tags matching catalog entities. It stores the VS Code launch URL so the frontend can surface it. Together they give you a smooth path from service catalog to a working editor session.

Common use cases include faster onboarding, a clean way to standardize local environments across teams, and fewer support pings about setup. It is also a low risk way to prove one click project start on a self hosted Backstage. You can keep using your current repos. You can open code from the catalog and get to work.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install the frontend package

  1. From the Backstage root run

    Copy
    yarn --cwd packages/app add @coder/backstage-plugin-devcontainers-react
  2. Open packages app src components catalog EntityPage.tsx

  3. Import the provider. Add it to a section of the entity page so users can see it.

    Copy
    // packages/app/src/components/catalog/EntityPage.tsx
    
    import React from 'react';
    import Grid from '@material-ui/core/Grid';
    import {
      DevcontainersProvider,
      type DevcontainersConfig,
    } from '@coder/backstage-plugin-devcontainers-react';
    
    // Match this tag to your backend config if you use the backend plugin
    const devcontainersConfig: DevcontainersConfig = {
      tagName: 'devcontainers',
    };
    
    // Example placement in the Overview tab
    const overviewContent = (
      <Grid container spacing={3} alignItems="stretch">
        {entityWarningContent}
    
        <Grid item md={6} xs={12}>
          <DevcontainersProvider config={devcontainersConfig}>
            {/* Place components that use the Dev Containers context here */}
          </DevcontainersProvider>
        </Grid>
    
        {/* Other grid content */}
      </Grid>
    );

Show a working component

  1. Use the example component to try things quickly. Place it inside the provider on the entity page.

    Copy
    // packages/app/src/components/catalog/EntityPage.tsx
    
    import React from 'react';
    import Grid from '@material-ui/core/Grid';
    import {
      DevcontainersProvider,
      ExampleDevcontainersComponent,
      type DevcontainersConfig,
    } from '@coder/backstage-plugin-devcontainers-react';
    
    const devcontainersConfig: DevcontainersConfig = {
      tagName: 'devcontainers',
    };
    
    const overviewContent = (
      <Grid container spacing={3} alignItems="stretch">
        {entityWarningContent}
    
        <Grid item md={6} xs={12}>
          <DevcontainersProvider config={devcontainersConfig}>
            <ExampleDevcontainersComponent />
          </DevcontainersProvider>
        </Grid>
      </Grid>
    );

Build your own UI with the hook

  1. Create a simple component that reads the state from the hook.

    Copy
    // packages/app/src/components/devcontainers/YourDevcontainersWidget.tsx
    
    import React from 'react';
    import { useDevcontainers } from '@coder/backstage-plugin-devcontainers-react';
    
    export const YourDevcontainersWidget = () => {
      const state = useDevcontainers();
    
      if (!state.hasUrl) {
        return <p>No Dev Containers tag found on this entity</p>;
      }
    
      return (
        <a href={state.vsCodeUrl}>Open in VS Code Dev Containers</a>
      );
    };
  2. Render it inside the provider on the entity page.

    Copy
    // packages/app/src/components/catalog/EntityPage.tsx
    
    import { YourDevcontainersWidget } from '../devcontainers/YourDevcontainersWidget';
    
    // inside overviewContent
    <DevcontainersProvider config={devcontainersConfig}>
      <YourDevcontainersWidget />
    </DevcontainersProvider>;
  3. When you click the link, VS Code may ask you to install the Dev Containers extension. Follow the prompt if needed.

Install the backend package

The frontend works on its own. The backend helps by adding tags and the VS Code launch URL to entities. Install the backend package first.

  1. From the Backstage root run

    Copy
    yarn --cwd packages/backend add @coder/backstage-plugin-devcontainers-backend
  2. Set the same tag on the backend that you used in the frontend config. Use devcontainers unless you need a custom value.

Wire up the backend on the new backend system

The new backend system uses backend modules. Add the Dev Containers backend module to the backend builder. Use a matching tag.

  1. Open packages backend src index.ts

  2. Register the module. The exact export name can differ by version. The idea stays the same. Add the module to the backend and pass the tag if needed.

    Copy
    // packages/backend/src/index.ts
    
    import { createBackend } from '@backstage/backend-defaults';
    
    // The backend module export name can differ. Check the backend package exports.
    // Example shape shown below.
    import { devcontainersModule } from '@coder/backstage-plugin-devcontainers-backend';
    
    const backend = createBackend();
    
    // If the module accepts options you can pass the tag to keep frontend and backend in sync
    backend.add(devcontainersModule({ tagName: 'devcontainers' }));
    
    backend.start();
  3. If your project splits modules across files, register it next to other third party modules in the same place.

Wire up the backend on the legacy backend

The legacy backend wires plugins directly in the backend package. Add the Dev Containers backend where you set up catalog or routers.

  1. If the backend plugin provides a catalog processor add it in packages backend src plugins catalog.ts

    Copy
    // packages/backend/src/plugins/catalog.ts
    
    import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
    
    // The processor export name can differ. Example name shown below.
    import { DevcontainersProcessor } from '@coder/backstage-plugin-devcontainers-backend';
    
    export default async function createPlugin(env: any) {
      const builder = await CatalogBuilder.create(env);
    
      // Pass matching tag if supported by the processor
      builder.addProcessor(
        DevcontainersProcessor.fromConfig?.(env.config, { tagName: 'devcontainers' }) ??
        new DevcontainersProcessor({ tagName: 'devcontainers' }),
      );
    
      const { processingEngine, router } = await builder.build();
      await processingEngine.start();
      return router;
    }
  2. If the backend plugin exposes an HTTP router instead, mount it in packages backend src index.ts next to other routers.

    Copy
    // packages/backend/src/index.ts
    
    import { createServiceBuilder } from '@backstage/backend-common';
    import { Server } from 'http';
    
    // The router factory export name can differ. Example name shown below.
    import { createRouter as createDevcontainersRouter } from '@coder/backstage-plugin-devcontainers-backend';
    
    async function main(): Promise<Server> {
      const service = createServiceBuilder(module)
        .addRouter(
          '/api/devcontainers',
          await createDevcontainersRouter({
            // Supply the same deps you provide to other plugin routers
            logger,
            config,
            discovery,
            reader,
          }),
        );
    
      return await service.start();
    }
    
    main().catch(err => {
      process.exit(1);
    });
  3. Use only one wiring path that matches what the backend package exports in your version. Some versions provide a processor. Some provide a router. Some provide both.

Keep frontend and backend config in sync

  1. The frontend provider uses tagName in DevcontainersConfig

  2. The backend uses the same tag to decide which entities get marked

  3. Keep both set to the same value. Use devcontainers unless you need a custom value

Where users will see it

  1. Users will see the Dev Containers UI wherever you placed the provider and your components on the entity page

  2. A simple starting point is the Overview tab of packages app src components catalog EntityPage.tsx

  3. You can also place the provider and your components on any other entity page tab that makes sense

Changelog

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

Set up Backstage in minutes with Roadie