Plateforme de Développeur Interne IDP : Construire un Portail Développeur d'Entreprise avec Backstage 2026

DevOps

Plateforme de Développeur Interne IDP : Construire un Portail Développeur d'Entreprise avec Backstage

Vous venez de rejoindre une entreprise avec 200 microservices. Le premier jour, votre lead dit « déploie order-service ». Vous passez ensuite 3 heures à trouver le dépôt Git, 2 heures à trouver la configuration CI/CD, 1 heure à trouver la documentation API — la journée est finie avant d'avoir écrit une seule ligne de code.

C'est le quotidien sans une Plateforme de Développeur Interne (IDP). Backstage, le framework de portail développeur open-sourcé par Spotify, devient le standard de facto pour l'ingénierie de plateforme. Cet article présente 5 modèles fondamentaux pour construire un IDP de niveau entreprise depuis zéro.

Concepts Clés en Bref

Concept Description Analogie
IDP (Plateforme de Développeur Interne) Portail libre-service unifié pour les développeurs « App Store » d'entreprise
Backstage Framework IDP open source de Spotify Le Kubernetes de l'IDP
Software Catalog Registre de métadonnées pour tous les services « Pages Jaunes » des services
TechDocs Système de documentation technique basé sur Markdown Wiki de niveau ingénierie
Software Template Création de projet standardisée en un clic CLI de scaffolding basé sur le web
Plugin Module d'extension de Backstage Extension de navigateur

5 Principales Douleurs Sans IDP

  1. Difficulté de découverte des services : Des centaines de microservices, impossible de trouver à qui appartient quoi, comment déployer, où surveiller
  2. Documentation éparpillée : Confluence, GitLab Wiki, Notion... introuvable, incompréhensible
  3. Onboarding projet lent : Reconstruire le scaffolding à chaque fois, CI/CD, monitoring, configuration du logging est répétitif
  4. Chaîne d'outils fragmentée : Jenkins, ArgoCD, Grafana, Jira... les changements de contexte constants tuent la productivité
  5. Cauchemar d'audit de conformité : Qui a changé quoi, qui a accès, le graphe de dépendances des services est un chaos

Modèle 1 : Architecture et Installation de Backstage

Backstage utilise une architecture monolithique basée sur les plugins. Le noyau se compose de quatre modules : Catalog + TechDocs + Scaffold + Search, étendus via des plugins.

# Installation et initialisation de Backstage
# Environnement : Node.js 20.x+ / yarn 1.22+ / Git 2.40+
# Dépendances : @backstage/cli 1.32+ / @backstage/core 1.30+

# 1. Créer une application Backstage
npx @backstage/create-app@latest --name my-idp
cd my-idp

# 2. Structure du projet
# my-idp/
# ├── app/                    # Application React frontend
# │   ├── src/
# │   │   ├── components/     # Composants personnalisés
# │   │   ├── plugins/        # Plugins frontend
# │   │   └── App.tsx         # Point d'entrée de l'application
# ├── packages/
# │   ├── backend/            # Service backend Node.js
# │   └── app/                # Package de build frontend
# ├── plugins/                # Répertoire de plugins personnalisés
# ├── catalog-info.yaml       # Définitions d'entités du catalogue
# └── app-config.yaml         # Configuration de l'application

# 3. Démarrer le serveur de développement
yarn dev

# 4. Construire la version de production
yarn build:backend
# app-config.yaml : Configuration principale de Backstage
# Environnement : Backstage 1.32+
app:
  title: Portail Développeur d'Entreprise
  baseUrl: http://localhost:3000

organization:
  name: MyCompany

backend:
  baseUrl: http://localhost:7007
  listen:
    port: 7007
  csp:
    connect-src: ["'self'", 'http:', 'https:']
  cors:
    origin: http://localhost:3000
    methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
    credentials: true
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT:-5432}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
      database: backstage_plugin

integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}
  gitlab:
    - host: gitlab.mycompany.com
      token: ${GITLAB_TOKEN}

auth:
  environment: development
  providers:
    github:
      development:
        clientId: ${GITHUB_CLIENT_ID}
        clientSecret: ${GITHUB_CLIENT_SECRET}
    oidc:
      production:
        metadataUrl: ${OIDC_METADATA_URL}
        clientId: ${OIDC_CLIENT_ID}
        clientSecret: ${OIDC_CLIENT_SECRET}

catalog:
  rules:
    - allow: [Component, System, API, Resource, Location, Template]
  locations:
    - type: url
      target: https://github.com/mycompany/catalog/blob/main/catalog-info.yaml
    - type: url
      target: https://gitlab.mycompany.com/platform/catalog/-/blob/main/all.yaml

techdocs:
  builder: 'local'
  generator:
    runIn: 'docker'
  publisher:
    type: 'local'

Modèle 2 : Software Catalog

Le Catalog est le cœur de Backstage — le registre de métadonnées pour tous les services. Définissez des entités via des fichiers descripteurs YAML, avec découverte et indexation automatiques des services.

# catalog-info.yaml : Enregistrement du composant de service
# Environnement : Backstage 1.32+ / Catalog API v1
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: order-service
  description: Service de commandes principal gérant la création, le paiement et l'exécution des commandes
  annotations:
    backstage.io/source-location: url:https://github.com/mycompany/order-service
    backstage.io/techdocs-ref: dir:.
    github.com/project-slug: mycompany/order-service
    gitlab.com/project-id: "12345"
    jira.com/project-key: ORD
    grafana/dashboard-tag: order-service
    prometheus.io/alert: order-service-alerts
  tags:
    - java
    - spring-boot
    - microservice
    - tier-1
  links:
    - url: https://grafana.mycompany.com/d/order-service
      title: Supervision Grafana
      icon: dashboard
    - url: https://argocd.mycompany.com/applications/order-service
      title: Déploiement ArgoCD
      icon: cloud
    - url: https://order-service.mycompany.com/swagger
      title: Documentation API
      icon: api
spec:
  type: service
  lifecycle: production
  owner: team-order
  system: order-system
  dependsOn:
    - component:payment-service
    - component:inventory-service
    - resource:order-db
  providesApis:
    - order-api
  consumesApis:
    - payment-api
    - inventory-api
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: order-api
  description: API REST du Service de Commandes
spec:
  type: openapi
  lifecycle: production
  owner: team-order
  system: order-system
  definition:
    $text: https://raw.githubusercontent.com/mycompany/order-service/main/openapi.yaml
---
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  name: order-system
  description: Système de commandes incluant les services de commandes, paiements et inventaire
spec:
  owner: team-order
  domain: commerce
---
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: team-order
  description: Équipe Commandes
spec:
  type: team
  parent: dept-commerce
  children: []
  members:
    - john.doe
    - jane.smith
    - bob.wilson
// TypeScript : Processeur personnalisé d'entités du catalogue
// Environnement : Node.js 20.x+ / @backstage/plugin-catalog-node 1.12+
import {
  CatalogProcessor,
  CatalogProcessorEmit,
  processingResult,
} from '@backstage/plugin-catalog-node';
import { LocationSpec, Location } from '@backstage/plugin-catalog-common';
import { Entity } from '@backstage/catalog-model';

export class CustomServiceProcessor implements CatalogProcessor {
  getProcessorName(): string {
    return 'CustomServiceProcessor';
  }

  async validateLocationKind(location: Location): Promise<boolean> {
    return location.type === 'url';
  }

  async preProcessEntity(
    entity: Entity,
    location: LocationSpec,
    emit: CatalogProcessorEmit,
  ): Promise<Entity> {
    // Ajouter automatiquement les tags de conformité pour tous les services en production
    if (
      entity.kind === 'Component' &&
      entity.spec?.lifecycle === 'production'
    ) {
      const annotations = entity.metadata.annotations ?? {};
      annotations['compliance.mycompany.com/scan-required'] = 'true';
      annotations['compliance.mycompany.com/last-scan'] = new Date().toISOString();
      entity.metadata.annotations = annotations;
    }

    // Générer automatiquement les relations de dépendance du service
    if (entity.kind === 'Component' && entity.spec?.dependsOn) {
      const deps = entity.spec.dependsOn as string[];
      for (const dep of deps) {
        emit(
          processingResult.relation({
            source: {
              name: entity.metadata.name,
              namespace: entity.metadata.namespace ?? 'default',
              kind: entity.kind,
            },
            target: {
              name: dep.replace('component:', ''),
              namespace: 'default',
              kind: 'Component',
            },
            type: 'dependsOn',
          }),
        );
      }
    }

    return entity;
  }
}

Modèle 3 : TechDocs

TechDocs est construit sur MkDocs + Markdown, suivant la philosophie Docs-as-Code — la documentation vit alongside le code dans le même cycle de vie du dépôt.

# mkdocs.yaml : Configuration TechDocs
# Environnement : MkDocs 1.6+ / mkdocs-material 9.5+
site_name: Documentation Technique du Service de Commandes
site_description: Documentation technique complète pour le Service de Commandes

nav:
  - Accueil: index.md
  - Démarrage: getting-started.md
  - Architecture:
    - Vue d'ensemble du système: architecture/overview.md
    - Modèle de données: architecture/data-model.md
    - Conception API: architecture/api-design.md
  - Exploitation:
    - Guide de déploiement: ops/deployment.md
    - Supervision et alertes: ops/monitoring.md
    - Dépannage: ops/troubleshooting.md
  - Développement:
    - Standards de code: dev/coding-standards.md
    - Workflow Git: dev/git-workflow.md
    - Stratégie de test: dev/testing.md

plugins:
  - techdocs-core:
      add_mermaid: true
      add_plantuml: true

markdown_extensions:
  - admonition
  - codehilite
  - footnotes
  - meta
  - toc:
      permalink: true
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
<!-- docs/index.md : Page d'accueil TechDocs -->
# Service de Commandes

## Vue d'ensemble

Le Service de Commandes est le microservice principal du système de commandes, gérant la création, le paiement et l'exécution des commandes.

## Liens rapides

| Ressource | Lien |
|----------|------|
| Code source | [GitHub](https://github.com/mycompany/order-service) |
| CI/CD | [Jenkins](https://jenkins.mycompany.com/job/order-service) |
| Supervision | [Grafana](https://grafana.mycompany.com/d/order-service) |
| Documentation API | [Swagger](https://order-service.mycompany.com/swagger) |

## Architecture

```mermaid
graph TB
    A[API Gateway] --> B[Order Service]
    B --> C[Payment Service]
    B --> D[Inventory Service]
    B --> E[(Order DB)]
    B --> F[Redis Cache]
    B --> G[Kafka]

Métriques clés

Métrique Cible Actuel
Latence P99 <200ms 150ms
Disponibilité 99.95% 99.97%
Taux d'erreur <0.1% 0.05%

## Modèle 4 : Développement de Plugins

Le système de plugins de Backstage est son avantage concurrentiel principal. Les plugins personnalisés permettent à l'IDP de s'adapter véritablement à votre chaîne d'outils d'entreprise.

```typescript
// TypeScript : Plugin personnalisé Backstage - Panneau de vérification de santé du service
// Environnement : Node.js 20.x+ / @backstage/core-plugin-api 1.9+ / React 18.x

// --- Plugin Frontend ---

// plugins/service-health/src/plugin.ts
import {
  createPlugin,
  createRoutableExtension,
} from '@backstage/core-plugin-api';

export const serviceHealthPlugin = createPlugin({
  id: 'service-health',
  routes: {
    root: rootRouteRef,
  },
});

export const ServiceHealthPage = serviceHealthPlugin.provide(
  createRoutableExtension({
    name: 'ServiceHealthPage',
    component: () =>
      import('./components/ServiceHealthPanel').then(
        (m) => m.ServiceHealthPanel,
      ),
    mountPoint: rootRouteRef,
  }),
);

// plugins/service-health/src/components/ServiceHealthPanel.tsx
import React from 'react';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
  Header,
  Page,
  Content,
  InfoCard,
  Progress,
  StatusOK,
  StatusError,
  StatusWarning,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { serviceHealthApiRef } from '../api';

interface HealthCheckResult {
  name: string;
  status: 'healthy' | 'degraded' | 'down';
  latency_ms: number;
  last_check: string;
  message: string;
}

export const ServiceHealthPanel: React.FC = () => {
  const { entity } = useEntity();
  const healthApi = useApi(serviceHealthApiRef);
  const [healthData, setHealthData] = React.useState<HealthCheckResult[]>([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    const serviceName = entity.metadata.name;
    healthApi
      .getServiceHealth(serviceName)
      .then((data) => {
        setHealthData(data.checks);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [entity, healthApi]);

  if (loading) return <Progress />;

  const StatusIcon = ({ status }: { status: string }) => {
    switch (status) {
      case 'healthy':
        return <StatusOK />;
      case 'degraded':
        return <StatusWarning />;
      case 'down':
        return <StatusError />;
      default:
        return null;
    }
  };

  return (
    <Page themeId="tool">
      <Header title={`Vérification de santé de ${entity.metadata.name}`} />
      <Content>
        {healthData.map((check) => (
          <InfoCard key={check.name} title={check.name}>
            <StatusIcon status={check.status} />
            <p>Statut : {check.status}</p>
            <p>Latence : {check.latency_ms}ms</p>
            <p>Dernière vérification : {check.last_check}</p>
            <p>Message : {check.message}</p>
          </InfoCard>
        ))}
      </Content>
    </Page>
  );
};
// TypeScript : Plugin backend Backstage
// Environnement : Node.js 20.x+ / @backstage/backend-common 0.25+

// plugins/service-health-backend/src/plugin.ts
import {
  createBackendPlugin,
  coreServices,
} from '@backstage/backend-plugin-api';
import { createRouter } from './router';

export const serviceHealthBackendPlugin = createBackendPlugin({
  pluginId: 'service-health',
  register(env) {
    env.registerInit({
      deps: {
        logger: coreServices.logger,
        config: coreServices.rootConfig,
        httpRouter: coreServices.httpRouter,
        discovery: coreServices.discovery,
      },
      async init({ logger, config, httpRouter, discovery }) {
        const router = await createRouter({
          logger,
          config,
          discovery,
        });

        httpRouter.use(router);
        httpRouter.addAuthPolicy({
          path: '/health',
          allow: 'unauthenticated',
        });

        logger.info('Plugin de santé du service initialisé');
      },
    });
  },
});

// plugins/service-health-backend/src/router.ts
import express, { Router } from 'express';
import { LoggerService, RootConfigService, DiscoveryService } from '@backstage/backend-plugin-api';

interface RouterOptions {
  logger: LoggerService;
  config: RootConfigService;
  discovery: DiscoveryService;
}

export async function createRouter(
  options: RouterOptions,
): Promise<Router> {
  const { logger, config } = options;
  const router = Router();

  router.get('/health', (_req, res) => {
    res.json({ status: 'ok' });
  });

  router.get('/services/:serviceName/health', async (req, res) => {
    const { serviceName } = req.params;

    try {
      const healthChecks = await fetchServiceHealth(config, serviceName);
      res.json({
        service: serviceName,
        checks: healthChecks,
        timestamp: new Date().toISOString(),
      });
    } catch (error) {
      logger.error(`Échec de récupération de l'état de santé de ${serviceName} : ${error}`);
      res.status(500).json({ error: 'Health check failed' });
    }
  });

  return router;
}

async function fetchServiceHealth(
  config: RootConfigService,
  serviceName: string,
): Promise<Array<{
  name: string;
  status: 'healthy' | 'degraded' | 'down';
  latency_ms: number;
  last_check: string;
  message: string;
}>> {
  const prometheusUrl = config.getString('monitoring.prometheus.url');

  return [
    {
      name: 'HTTP Endpoint',
      status: 'healthy',
      latency_ms: 45,
      last_check: new Date().toISOString(),
      message: 'Tous les endpoints répondent normalement',
    },
    {
      name: 'Database Connection',
      status: 'healthy',
      latency_ms: 12,
      last_check: new Date().toISOString(),
      message: 'Pool de connexions sain',
    },
    {
      name: 'Kafka Consumer',
      status: 'degraded',
      latency_ms: 250,
      last_check: new Date().toISOString(),
      message: 'Le lag du consommateur dépasse le seuil',
    },
  ];
}

Modèle 5 : Déploiement IDP de Niveau Production

# docker-compose.yml : Déploiement en production de Backstage
# Environnement : Docker Compose v2.30+ / PostgreSQL 16
version: "3.8"

services:
  backstage:
    image: mycompany/backstage:1.32.0
    ports:
      - "7007:7007"
    environment:
      APP_CONFIG_app_baseUrl: https://idp.mycompany.com
      APP_CONFIG_backend_baseUrl: https://idp.mycompany.com
      APP_CONFIG_backend_database_client: pg
      APP_CONFIG_backend_database_connection_host: postgres
      APP_CONFIG_backend_database_connection_port: "5432"
      APP_CONFIG_backend_database_connection_user: ${POSTGRES_USER}
      APP_CONFIG_backend_database_connection_password: ${POSTGRES_PASSWORD}
      POSTGRES_HOST: postgres
      POSTGRES_PORT: "5432"
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      GITHUB_TOKEN: ${GITHUB_TOKEN}
      GITLAB_TOKEN: ${GITLAB_TOKEN}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:7007/healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 3

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: backstage_plugin
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres_data:
# kubernetes/backstage-deployment.yaml : Déploiement K8s en production
# Environnement : Kubernetes 1.30+ / Helm 3.15+
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backstage
  namespace: idp
  labels:
    app: backstage
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backstage
  template:
    metadata:
      labels:
        app: backstage
    spec:
      containers:
        - name: backstage
          image: mycompany/backstage:1.32.0
          ports:
            - containerPort: 7007
          env:
            - name: APP_CONFIG_app_baseUrl
              value: "https://idp.mycompany.com"
            - name: APP_CONFIG_backend_baseUrl
              value: "https://idp.mycompany.com"
            - name: APP_CONFIG_backend_database_client
              value: "pg"
            - name: APP_CONFIG_backend_database_connection_host
              valueFrom:
                secretKeyRef:
                  name: backstage-db-secret
                  key: host
            - name: APP_CONFIG_backend_database_connection_password
              valueFrom:
                secretKeyRef:
                  name: backstage-db-secret
                  key: password
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2000m"
              memory: "2Gi"
          livenessProbe:
            httpGet:
              path: /healthcheck
              port: 7007
            initialDelaySeconds: 30
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /healthcheck
              port: 7007
            initialDelaySeconds: 10
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: backstage
  namespace: idp
spec:
  selector:
    app: backstage
  ports:
    - port: 80
      targetPort: 7007
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backstage
  namespace: idp
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - idp.mycompany.com
      secretName: backstage-tls
  rules:
    - host: idp.mycompany.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backstage
                port:
                  number: 80
# Déploiement avec Helm (recommandé)
# Environnement : Helm 3.15+ / Kubernetes 1.30+

# 1. Ajouter le dépôt Helm de Backstage
helm repo add backstage https://backstage.github.io/charts
helm repo update

# 2. Créer le fichier de valeurs override
cat > backstage-values.yaml << 'EOF'
backstage:
  image:
    repository: mycompany/backstage
    tag: 1.32.0
  extraAppConfig:
    - configMapRef: backstage-config
  extraEnvVarsSecrets:
    - backstage-secrets

ingress:
  enabled: true
  host: idp.mycompany.com
  tls:
    enabled: true
    secretName: backstage-tls

postgresql:
  enabled: true
  primary:
    persistence:
      size: 20Gi
  auth:
    existingSecret: backstage-db-secret
EOF

# 3. Déployer
helm install backstage backstage/backstage \
  -n idp \
  --create-namespace \
  -f backstage-values.yaml

# 4. Vérifier
kubectl get pods -n idp
kubectl port-forward svc/backstage 7007:80 -n idp

Guide des Pièges : 5 Pièges Courants

Piège 1 : L'entité du catalogue n'apparaît pas après l'enregistrement

❌ Faux : Créer seulement catalog-info.yaml localement sans enregistrer un Location
✅ Juste : Ajouter des locations dans app-config.yaml, ou enregistrer le Location dans l'UI

Piège 2 : Échecs de build TechDocs

❌ Faux : Exécuter MkDocs directement sur l'hôte, les dépendances manquantes causent des échecs de build
✅ Juste : Configurer techdocs.generator.runIn: 'docker' pour les builds conteneurisés

Piège 3 : Permissions insuffisantes du token d'intégration GitHub/GitLab

# ❌ Faux : Le token n'a que la permission repo:read
# ✅ Juste : Le token nécessite ces permissions
# GitHub : repo, read:org, read:user
# GitLab : api, read_repository, read_api

Piège 4 : Le hot reload du plugin ne fonctionne pas

# ❌ Faux : Ne pas redémarrer le serveur de développement après modification du code du plugin
# ✅ Juste : S'assurer que yarn dev est en cours d'exécution, Backstage supporte le HMR hot reload
# Si le HMR ne fonctionne pas, vider le cache et redémarrer :
yarn cache clean
yarn dev

Piège 5 : Épuisement du pool de connexions de base de données en production

# app-config.yaml : Configurer le pool de connexions
backend:
  database:
    client: pg
    connection:
      host: postgres
      port: 5432
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
      pool:
        min: 5
        max: 20
        idleTimeoutMillis: 30000
        connectionTimeoutMillis: 5000

Tableau de Dépannage des Erreurs

Message d'erreur Cause Solution
Failed to fetch catalog entities Token GitHub/GitLab invalide ou permissions insuffisantes Vérifier les permissions du token, s'assurer de repo et read:org
ECONNREFUSED 127.0.0.1:5432 PostgreSQL non démarré ou configuration de connexion incorrecte Vérifier le service de base de données et les paramètres de connexion
TechDocs generation failed Échec du build MkDocs, dépendances manquantes Configurer runIn: 'docker', ou installer les packages Python manquants
Plugin not found: xxx Plugin non enregistré dans le backend Importer et enregistrer le plugin dans packages/backend/src/index.ts
Unauthorized: Invalid token Mauvaise configuration OIDC/OAuth Vérifier clientId/clientSecret et l'URL de callback
CORS error Incompatibilité de domaine frontend/backend Configurer backend.cors.origin pour correspondre à l'URL du frontend
Location not found URL catalog-info.yaml inaccessible Vérifier l'accessibilité de l'URL et les permissions du token
Out of memory Mémoire heap Node.js insuffisante Configurer NODE_OPTIONS=--max-old-space-size=4096
Schema validation failed Erreur de format catalog-info.yaml Valider avec backstage-cli catalog-validate
Template execution timeout Délai d'exécution du Software Template dépassé Vérifier les requêtes HTTP dans le template, augmenter la configuration de timeout

Optimisation Avancée : 5 Conseils de Niveau Production

Conseil 1 : Auto-découverte du catalogue

// TypeScript : Processeur d'auto-découverte GitLab
// Environnement : @backstage/plugin-catalog-backend-module-gitlab 0.8+
import { GitLabDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-gitlab';

const config = {
  catalog: {
    processors: [
      GitLabDiscoveryProcessor.fromConfig(env.config, {
        logger: env.logger,
        schedule: {
          frequency: { minutes: 30 },
          timeout: { minutes: 3 },
          initialDelay: { seconds: 15 },
        },
      }),
    ],
  },
};

Conseil 2 : Contrôle d'accès RBAC

# app-config.yaml : Configuration RBAC
# Environnement : @backstage/plugin-permission-backend 0.6+
permission:
  enabled: true
  rbac:
    policies:
      - name: 'role:default/developer'
        permissions:
          - 'catalog.entity.read'
          - 'techdocs.read'
          - 'scaffolder.template.execute'
      - name: 'role:default/admin'
        permissions:
          - 'catalog.entity.create'
          - 'catalog.entity.delete'
          - 'catalog.entity.update'
          - 'techdocs.write'
          - 'scaffolder.template.create'
    policyFile: ./rbac-policy.csv

Conseil 3 : Amélioration de la recherche

# app-config.yaml : Configuration du moteur de recherche
# Environnement : @backstage/plugin-search-backend-module-elasticsearch 1.6+
search:
  collators:
    techdocs:
      schedule:
        frequency: { minutes: 10 }
        timeout: { minutes: 5 }
        initialDelay: { seconds: 30 }
  elasticsearch:
    url: ${ELASTICSEARCH_URL}
    auth:
      username: ${ELASTICSEARCH_USER}
      password: ${ELASTICSEARCH_PASSWORD}
    indexPrefix: backstage_search_

Conseil 4 : Intégration ArgoCD multi-cluster

# app-config.yaml : Intégration multi-cluster ArgoCD
# Environnement : @backstage/plugin-argocd 2.5+
argocd:
  appLocatorMethods:
    - type: 'config'
      instances:
        - name: production
          url: https://argocd.mycompany.com
          token: ${ARGOCD_TOKEN_PROD}
        - name: staging
          url: https://argocd-staging.mycompany.com
          token: ${ARGOCD_TOKEN_STAGING}

Conseil 5 : Templates Golden Path

# templates/java-spring-boot/template.yaml : Template de scaffolding Golden Path
# Environnement : @backstage/plugin-scaffolder 1.25+
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: java-spring-boot-service
  title: Template de Microservice Java Spring Boot
  description: Créer un microservice Spring Boot standardisé avec CI/CD, monitoring et documentation
spec:
  owner: platform-team
  type: service
  parameters:
    - title: Informations du service
      required:
        - name
        - owner
      properties:
        name:
          title: Nom du service
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
          description: Uniquement des minuscules, chiffres et tirets
        owner:
          title: Équipe propriétaire
          type: string
          ui:field: OwnerPicker
          ui:options:
            allowedKinds:
              - Group
        description:
          title: Description du service
          type: string
          description: Description en une ligne de la fonctionnalité du service
        database:
          title: Type de base de données
          type: string
          default: postgresql
          enum:
            - postgresql
            - mysql
            - mongodb
            - none
    - title: Configuration CI/CD
      properties:
        ciProvider:
          title: Outil CI
          type: string
          default: github-actions
          enum:
            - github-actions
            - gitlab-ci
            - jenkins
        deployTarget:
          title: Cible de déploiement
          type: string
          default: kubernetes
          enum:
            - kubernetes
            - ecs
            - cloud-run

  steps:
    - id: fetch-template
      name: Récupérer le code du template
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
          description: ${{ parameters.description }}
          database: ${{ parameters.database }}
          ciProvider: ${{ parameters.ciProvider }}
          deployTarget: ${{ parameters.deployTarget }}

    - id: publish
      name: Publier dans le dépôt Git
      action: publish:github
      input:
        allowedHosts: ['github.com']
        description: ${{ parameters.description }}
        repoUrl: github.com?owner=mycompany&repo=${{ parameters.name }}
        defaultBranch: main
        protectDefaultBranch: true

    - id: register
      name: Enregistrer dans le catalogue
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: '/catalog-info.yaml'

  output:
    links:
      - title: URL du dépôt
        url: ${{ steps.publish.output.remoteUrl }}
      - title: Ouvrir dans le catalogue
        url: https://idp.mycompany.com/catalog/default/component/${{ parameters.name }}

Comparaison des Plateformes IDP

Dimension Backstage Port Humanitec
Open Source ✅ Apache 2.0 ❌ SaaS Commercial ❌ SaaS Commercial
Self-Hosted ✅ Contrôle total ❌ Hébergé dans le cloud ❌ Hébergé dans le cloud
Écosystème de plugins 200+ plugins communautaires Intégrations intégrées Intégrations intégrées
Courbe d'apprentissage Moyenne-Élevée (TypeScript) Basse (configuration visuelle) Moyenne (ingénierie de plateforme)
Personnalisation Très élevée (niveau code) Moyenne (extension API) Moyenne (définition Score)
Software Catalog ✅ Fonctionnalité principale ✅ Fonctionnalité principale ✅ Fonctionnalité principale
TechDocs ✅ Support natif ❌ Nécessite une intégration ❌ Nécessite une intégration
Coût Effort d'ingénierie 50-200$/utilisateur/mois 100-500$/utilisateur/mois
Échelle adaptée 50-5000+ développeurs 10-500 développeurs 100-5000+ développeurs
Clients notables Spotify, American Airlines Lyft, Palo Alto BMW, Qonto

Conclusion

Backstage est le choix optimal pour construire une Plateforme de Développeur Interne en 2026 — open source, auto-hébergeable, avec un riche écosystème de plugins. Mais notez :

  • Petites équipes (<50) : Envisagez d'abord Port ou des solutions SaaS similaires ; la maintenance de Backstage peut ne pas être rentable
  • Équipes moyennes (50-500) : Backstage est le meilleur choix, 2-3 ingénieurs pour la maintenance
  • Grandes équipes (500+) : Backstage + templates Golden Path + auto-découverte pour un système complet d'ingénierie de plateforme

Facteurs clés de succès : Commencez avec Catalog et TechDocs, ajoutez progressivement des plugins, puis construisez des templates Golden Path. Ne cherchez pas à faire bouillir l'océan.

Outils en Ligne Recommandés

  • /fr/json/format - Formateur JSON pour la conversion YAML-vers-JSON des entités du catalogue
  • /fr/dev/curl-to-code - Générateur de code depuis cURL pour les appels API Backstage
  • /fr/encode/hash - Calculateur de hash pour générer des identifiants de service uniques
  • /fr/text/diff - Diff de texte pour comparer les modifications de catalog-info.yaml

Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →

#内部开发者平台#Backstage#IDP#开发者门户#平台工程#2026#DevOps运维