Plataforma de Desenvolvedor Interna IDP: Construindo um Portal de Desenvolvedor Empresarial com Backstage 2026

DevOps

Plataforma de Desenvolvedor Interna IDP: Construindo um Portal de Desenvolvedor Empresarial com Backstage

Você acabou de entrar em uma empresa com 200 microsserviços. No primeiro dia, seu líder diz "implante o order-service". Então você gasta 3 horas encontrando o repositório Git, 2 horas encontrando a configuração CI/CD, 1 hora encontrando a documentação da API — o dia acabou antes de escrever uma única linha de código.

Esta é a vida diária sem uma Plataforma de Desenvolvedor Interna (IDP). Backstage, o framework de portal de desenvolvedores de código aberto criado pelo Spotify, está se tornando o padrão de fato para engenharia de plataformas. Este artigo percorre 5 padrões principais para construir um IDP de nível empresarial do zero.

Conceitos Principais de Relance

Conceito Descrição Analogia
IDP (Plataforma de Desenvolvedor Interna) Portal de autoatendimento unificado para desenvolvedores "App Store" empresarial
Backstage Framework IDP de código aberto do Spotify O Kubernetes do IDP
Software Catalog Registro de metadados para todos os serviços "Páginas Amarelas" para serviços
TechDocs Sistema de documentação técnica baseado em Markdown Wiki de nível de engenharia
Software Template Criação padronizada de projetos com um clique CLI de scaffolding baseado na web
Plugin Módulo de extensão do Backstage Extensão de navegador

5 Principais Pontos de Dor Sem um IDP

  1. Dificuldade na descoberta de serviços: Centenas de microsserviços, não consegue encontrar quem é dono do quê, como implantar, onde monitorar
  2. Documentação espalhada: Confluence, GitLab Wiki, Notion... não encontra, não entende
  3. Onboarding lento de projetos: Reconstruir scaffolding do zero toda vez, CI/CD, monitoramento, configuração de logging é repetitivo
  4. Cadeia de ferramentas fragmentada: Jenkins, ArgoCD, Grafana, Jira... troca constante de contexto mata a produtividade
  5. Pesadelo de auditoria de conformidade: Quem mudou o quê, quem tem acesso, o grafo de dependências de serviços é uma bagunça

Padrão 1: Arquitetura e Instalação do Backstage

O Backstage utiliza uma arquitetura monolítica baseada em plugins. O núcleo consiste em quatro módulos: Catalog + TechDocs + Scaffold + Search, estendidos através de plugins.

# Instalação e inicialização do Backstage
# Ambiente: Node.js 20.x+ / yarn 1.22+ / Git 2.40+
# Dependências: @backstage/cli 1.32+ / @backstage/core 1.30+

# 1. Criar aplicação Backstage
npx @backstage/create-app@latest --name my-idp
cd my-idp

# 2. Estrutura do projeto
# my-idp/
# ├── app/                    # Aplicação React frontend
# │   ├── src/
# │   │   ├── components/     # Componentes personalizados
# │   │   ├── plugins/        # Plugins frontend
# │   │   └── App.tsx         # Ponto de entrada da aplicação
# ├── packages/
# │   ├── backend/            # Serviço backend Node.js
# │   └── app/                # Pacote de build frontend
# ├── plugins/                # Diretório de plugins personalizados
# ├── catalog-info.yaml       # Definições de entidades do catálogo
# └── app-config.yaml         # Configuração da aplicação

# 3. Iniciar servidor de desenvolvimento
yarn dev

# 4. Construir versão de produção
yarn build:backend
# app-config.yaml: Configuração principal do Backstage
# Ambiente: Backstage 1.32+
app:
  title: Portal de Desenvolvedor Empresarial
  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'

Padrão 2: Software Catalog

O Catalog é o coração do Backstage — o registro de metadados para todos os serviços. Define entidades através de arquivos descritores YAML, com descoberta e indexação automática de serviços.

# catalog-info.yaml: Registro de componente de serviço
# Ambiente: Backstage 1.32+ / Catalog API v1
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: order-service
  description: Serviço de pedidos principal que gerencia criação, pagamento e fulfillment de pedidos
  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: Monitoramento Grafana
      icon: dashboard
    - url: https://argocd.mycompany.com/applications/order-service
      title: Implantação ArgoCD
      icon: cloud
    - url: https://order-service.mycompany.com/swagger
      title: Docs 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 do Serviço de Pedidos
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: Sistema de pedidos incluindo serviços de pedidos, pagamentos e inventário
spec:
  owner: team-order
  domain: commerce
---
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: team-order
  description: Equipe de Pedidos
spec:
  type: team
  parent: dept-commerce
  children: []
  members:
    - john.doe
    - jane.smith
    - bob.wilson
// TypeScript: Processador personalizado de entidades do Catalog
// Ambiente: 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> {
    // Adicionar automaticamente tags de conformidade para todos os serviços em produção
    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;
    }

    // Gerar automaticamente relações de dependência do serviço
    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;
  }
}

Padrão 3: TechDocs

O TechDocs é construído sobre MkDocs + Markdown, seguindo a filosofia Docs-as-Code — a documentação vive junto ao código no mesmo ciclo de vida do repositório.

# mkdocs.yaml: Configuração do TechDocs
# Ambiente: MkDocs 1.6+ / mkdocs-material 9.5+
site_name: Documentação Técnica do Serviço de Pedidos
site_description: Documentação técnica completa para o Serviço de Pedidos

nav:
  - Início: index.md
  - Primeiros Passos: getting-started.md
  - Arquitetura:
    - Visão Geral do Sistema: architecture/overview.md
    - Modelo de Dados: architecture/data-model.md
    - Design de API: architecture/api-design.md
  - Operações:
    - Guia de Implantação: ops/deployment.md
    - Monitoramento e Alertas: ops/monitoring.md
    - Solução de Problemas: ops/troubleshooting.md
  - Desenvolvimento:
    - Padrões de Código: dev/coding-standards.md
    - Fluxo Git: dev/git-workflow.md
    - Estratégia de Testes: 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: Página inicial do TechDocs -->
# Serviço de Pedidos

## Visão Geral

O Serviço de Pedidos é o microsserviço principal do sistema de pedidos, gerenciando criação, pagamento e fulfillment de pedidos.

## Links Rápidos

| Recurso | Link |
|----------|------|
| Código Fonte | [GitHub](https://github.com/mycompany/order-service) |
| CI/CD | [Jenkins](https://jenkins.mycompany.com/job/order-service) |
| Monitoramento | [Grafana](https://grafana.mycompany.com/d/order-service) |
| Docs API | [Swagger](https://order-service.mycompany.com/swagger) |

## Arquitetura

```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étricas Chave

Métrica Objetivo Atual
Latência P99 <200ms 150ms
Disponibilidade 99.95% 99.97%
Taxa de Erro <0.1% 0.05%

## Padrão 4: Desenvolvimento de Plugins

O sistema de plugins do Backstage é sua principal vantagem competitiva. Plugins personalizados permitem que o IDP se adapte verdadeiramente à cadeia de ferramentas da sua empresa.

```typescript
// TypeScript: Plugin personalizado do Backstage - Painel de Verificação de Saúde do Serviço
// Ambiente: 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={`Verificação de Saúde de ${entity.metadata.name}`} />
      <Content>
        {healthData.map((check) => (
          <InfoCard key={check.name} title={check.name}>
            <StatusIcon status={check.status} />
            <p>Status: {check.status}</p>
            <p>Latência: {check.latency_ms}ms</p>
            <p>Última Verificação: {check.last_check}</p>
            <p>Mensagem: {check.message}</p>
          </InfoCard>
        ))}
      </Content>
    </Page>
  );
};
// TypeScript: Plugin backend do Backstage
// Ambiente: 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 Saúde do Serviço inicializado');
      },
    });
  },
});

// 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(`Falha ao obter saúde 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: 'Todos os endpoints respondendo normalmente',
    },
    {
      name: 'Database Connection',
      status: 'healthy',
      latency_ms: 12,
      last_check: new Date().toISOString(),
      message: 'Pool de conexões saudável',
    },
    {
      name: 'Kafka Consumer',
      status: 'degraded',
      latency_ms: 250,
      last_check: new Date().toISOString(),
      message: 'Lag do consumidor excede o limite',
    },
  ];
}

Padrão 5: Implantação de IDP de Nível Produção

# docker-compose.yml: Implantação em produção do Backstage
# Ambiente: 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: Implantação K8s em produção
# Ambiente: 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
# Implantação com Helm (recomendado)
# Ambiente: Helm 3.15+ / Kubernetes 1.30+

# 1. Adicionar repositório Helm do Backstage
helm repo add backstage https://backstage.github.io/charts
helm repo update

# 2. Criar arquivo de valores 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. Implantar
helm install backstage backstage/backstage \
  -n idp \
  --create-namespace \
  -f backstage-values.yaml

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

Guia de Armadilhas: 5 Armadilhas Comuns

Armadilha 1: Entidade do Catalog Não Aparece Após o Registro

❌ Errado: Apenas criar catalog-info.yaml localmente sem registrar um Location
✅ Certo: Adicionar locations no app-config.yaml, ou registrar Location na UI

Armadilha 2: Falhas na Construção do TechDocs

❌ Errado: Executar MkDocs diretamente no host, dependências faltantes causam falhas de build
✅ Certo: Configurar techdocs.generator.runIn: 'docker' para builds em contêiner

Armadilha 3: Permissões Insuficientes do Token de Integração GitHub/GitLab

# ❌ Errado: Token tem apenas permissão repo:read
# ✅ Certo: O Token precisa destas permissões
# GitHub: repo, read:org, read:user
# GitLab: api, read_repository, read_api

Armadilha 4: Hot Reload do Plugin Não Funciona

# ❌ Errado: Não reiniciar o servidor de desenvolvimento após modificar o código do plugin
# ✅ Certo: Garantir que yarn dev esteja rodando, Backstage suporta HMR hot reload
# Se HMR não funcionar, limpar cache e reiniciar:
yarn cache clean
yarn dev

Armadilha 5: Esgotamento do Pool de Conexões do Banco de Dados em Produção

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

Tabela de Solução de Erros

Mensagem de Erro Causa Solução
Failed to fetch catalog entities Token GitHub/GitLab inválido ou permissões insuficientes Verificar permissões do Token, garantir repo e read:org
ECONNREFUSED 127.0.0.1:5432 PostgreSQL não está rodando ou configuração de conexão incorreta Verificar serviço de banco de dados e parâmetros de conexão
TechDocs generation failed Falha no build MkDocs, dependências faltantes Configurar runIn: 'docker', ou instalar pacotes Python faltantes
Plugin not found: xxx Plugin não registrado no backend Importar e registrar plugin em packages/backend/src/index.ts
Unauthorized: Invalid token Configuração incorreta de OIDC/OAuth Verificar clientId/clientSecret e URL de callback
CORS error Incompatibilidade de domínio frontend/backend Configurar backend.cors.origin para corresponder à URL do frontend
Location not found URL do catalog-info.yaml inacessível Verificar acessibilidade da URL e permissões do Token
Out of memory Memória heap do Node.js insuficiente Configurar NODE_OPTIONS=--max-old-space-size=4096
Schema validation failed Erro de formato no catalog-info.yaml Validar com backstage-cli catalog-validate
Template execution timeout Tempo limite esgotado na execução do Software Template Verificar requisições HTTP no template, aumentar configuração de timeout

Otimização Avançada: 5 Dicas de Nível Produção

Dica 1: Auto-Descoberta do Catalog

// TypeScript: Processador de auto-descoberta do GitLab
// Ambiente: @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 },
        },
      }),
    ],
  },
};

Dica 2: Controle de Permissões RBAC

# app-config.yaml: Configuração RBAC
# Ambiente: @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

Dica 3: Melhoria de Busca

# app-config.yaml: Configuração do mecanismo de busca
# Ambiente: @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_

Dica 4: Integração ArgoCD Multi-Cluster

# app-config.yaml: Integração multi-cluster do ArgoCD
# Ambiente: @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}

Dica 5: Templates Golden Path

# templates/java-spring-boot/template.yaml: Template de scaffolding Golden Path
# Ambiente: @backstage/plugin-scaffolder 1.25+
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: java-spring-boot-service
  title: Template de Microsserviço Java Spring Boot
  description: Criar um microsserviço Spring Boot padronizado com CI/CD, monitoramento e documentação
spec:
  owner: platform-team
  type: service
  parameters:
    - title: Informações do Serviço
      required:
        - name
        - owner
      properties:
        name:
          title: Nome do Serviço
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
          description: Apenas letras minúsculas, números e hifens
        owner:
          title: Equipe Proprietária
          type: string
          ui:field: OwnerPicker
          ui:options:
            allowedKinds:
              - Group
        description:
          title: Descrição do Serviço
          type: string
          description: Descrição de uma linha da funcionalidade do serviço
        database:
          title: Tipo de Banco de Dados
          type: string
          default: postgresql
          enum:
            - postgresql
            - mysql
            - mongodb
            - none
    - title: Configuração CI/CD
      properties:
        ciProvider:
          title: Ferramenta CI
          type: string
          default: github-actions
          enum:
            - github-actions
            - gitlab-ci
            - jenkins
        deployTarget:
          title: Destino de Implantação
          type: string
          default: kubernetes
          enum:
            - kubernetes
            - ecs
            - cloud-run

  steps:
    - id: fetch-template
      name: Buscar Código do 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: Publicar no Repositório 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: Registrar no Catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: '/catalog-info.yaml'

  output:
    links:
      - title: URL do Repositório
        url: ${{ steps.publish.output.remoteUrl }}
      - title: Abrir no Catalog
        url: https://idp.mycompany.com/catalog/default/component/${{ parameters.name }}

Comparação de Plataformas IDP

Dimensão Backstage Port Humanitec
Código Aberto ✅ Apache 2.0 ❌ SaaS Comercial ❌ SaaS Comercial
Self-Hosted ✅ Controle total ❌ Hospedado na nuvem ❌ Hospedado na nuvem
Ecossistema de Plugins 200+ plugins comunitários Integrações integradas Integrações integradas
Curva de Aprendizado Média-Alta (TypeScript) Baixa (configuração visual) Média (engenharia de plataformas)
Personalização Muito Alta (nível de código) Média (extensão API) Média (definição Score)
Software Catalog ✅ Funcionalidade principal ✅ Funcionalidade principal ✅ Funcionalidade principal
TechDocs ✅ Suporte nativo ❌ Precisa de integração ❌ Precisa de integração
Custo Esforço de engenharia $50-200/usuário/mês $100-500/usuário/mês
Escala Adequada 50-5000+ desenvolvedores 10-500 desenvolvedores 100-5000+ desenvolvedores
Clientes Notáveis Spotify, American Airlines Lyft, Palo Alto BMW, Qonto

Conclusão

O Backstage é a escolha ótima para construir uma Plataforma de Desenvolvedor Interna em 2026 — de código aberto, auto-hospedável, com um rico ecossistema de plugins. Mas note:

  • Equipes pequenas (<50): Considere primeiro Port ou soluções SaaS similares; a manutenção do Backstage pode não ser custo-efetiva
  • Equipes médias (50-500): Backstage é a melhor escolha, 2-3 engenheiros para manutenção
  • Equipes grandes (500+): Backstage + templates Golden Path + auto-descoberta para um sistema completo de engenharia de plataformas

Fatores-chave de sucesso: Comece com Catalog e TechDocs, adicione plugins gradualmente, depois construa templates Golden Path. Não tente ferver o oceano.

Ferramentas Online Recomendadas

Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →

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