Quando uma organização tem dois ou três times de desenvolvimento, o modelo DevOps tradicional funciona bem: cada time opera sua própria infraestrutura, define seus próprios pipelines e resolve seus próprios problemas operacionais. O conhecimento é compartilhado informalmente, as ferramentas são razoavelmente padronizadas e o atrito é manejável.
À medida que a organização cresce — dez times, vinte, cinquenta — esse modelo começa a se fragmentar. Cada time reinventa a roda de maneiras ligeiramente diferentes: pipelines de CI/CD com estruturas inconsistentes, configurações de infraestrutura copiadas de repositórios uns dos outros com pequenas variações, práticas de segurança aplicadas de forma irregular, onboarding de novos desenvolvedores que leva semanas porque não há um caminho padronizado. O time de plataforma ou SRE se torna um gargalo — respondendo a perguntas repetitivas, revisando configurações similares, resolvendo os mesmos problemas em contextos diferentes.
O Platform Engineering é a disciplina que resolve esse problema construindo uma Internal Developer Platform — uma plataforma interna que abstrai a complexidade operacional e oferece aos times de desenvolvimento uma experiência de autoatendimento bem definida. O objetivo não é centralizar o controle, mas criar "paved roads" — caminhos pavimentados que tornam a maneira correta de fazer as coisas também a mais fácil.
O Conceito de Internal Developer Platform
Uma IDP — Internal Developer Platform — não é um único produto ou ferramenta. É um conjunto de capacidades, APIs, ferramentas e fluxos de trabalho que o time de plataforma constrói e mantém para que os times de produto possam ser autônomos sem precisar ser especialistas em infraestrutura.
As capacidades típicas de uma IDP madura incluem:
Self-service de infraestrutura — um desenvolvedor consegue provisionar um ambiente completo — banco de dados, cache, filas, monitoramento — sem abrir um ticket para o time de infraestrutura.
Templates de aplicação — novos serviços são criados a partir de templates que já incluem as melhores práticas de segurança, observabilidade e CI/CD.
Portal do desenvolvedor — uma interface centralizada onde o desenvolvedor encontra toda a informação relevante sobre os serviços que opera: status, métricas, logs, dependências, documentação, contato do time.
Catálogo de serviços — visibilidade de todos os serviços da organização, quem os possui, em que estado estão e como se comunicam entre si.
Backstage: O Portal do Desenvolvedor da Spotify
O Backstage é a plataforma de portal do desenvolvedor open-source criada pela Spotify e doada à CNCF. É a base sobre a qual a maioria das organizações constrói sua IDP — um framework extensível que oferece o catálogo de serviços, um sistema de templates e um marketplace de plugins.
Instalando e Configurando o Backstage
# Cria um novo app Backstage
npx @backstage/create-app@latest --path minha-plataforma
cd minha-plataforma
# Instala dependências
yarn install
# Inicia em modo de desenvolvimento
yarn dev
A estrutura do projeto Backstage:
minha-plataforma/
├── app-config.yaml # Configuração principal
├── app-config.production.yaml
├── packages/
│ ├── app/ # Frontend (React)
│ │ └── src/
│ │ ├── App.tsx
│ │ └── components/
│ └── backend/ # Backend (Node.js/Express)
│ └── src/
│ └── index.ts
└── plugins/ # Plugins customizados
Configuração principal do Backstage:
# app-config.yaml
app:
title: Plataforma de Desenvolvimento — Empresa
baseUrl: https://plataforma.empresa.com
organization:
name: Empresa S.A.
backend:
baseUrl: https://plataforma.empresa.com
listen:
port: 7007
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
database: backstage
cache:
store: redis
connection: ${REDIS_URL}
# Autenticação via GitHub OAuth
auth:
providers:
github:
development:
clientId: ${GITHUB_CLIENT_ID}
clientSecret: ${GITHUB_CLIENT_SECRET}
# Integração com GitHub para importar componentes
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
# Catálogo — onde o Backstage descobre os componentes
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow:
[
Component,
System,
API,
Resource,
Location,
Domain,
Group,
User,
]
locations:
# Importa todos os repositórios da organização com catalog-info.yaml
- type: github-org
target: https://github.com/empresa
rules:
- allow: [Group, User]
# Importa templates de scaffolding
- type: url
target: https://github.com/empresa/plataforma-templates/blob/main/all-templates.yaml
# TechDocs — documentação integrada
techdocs:
builder: external
generator:
runIn: local
publisher:
type: awsS3
awsS3:
bucketName: ${TECHDOCS_BUCKET}
region: us-east-1
# Configuração do Kubernetes para mostrar workloads
kubernetes:
serviceLocatorMethod:
type: multiTenant
clusterLocatorMethods:
- type: config
clusters:
- url: ${K8S_CLUSTER_URL}
name: producao
authProvider: serviceAccount
skipTLSVerify: false
serviceAccountToken: ${K8S_SERVICE_ACCOUNT_TOKEN}
caData: ${K8S_CA_DATA}
Registrando um Serviço no Catálogo
Cada repositório de serviço inclui um arquivo catalog-info.yaml que descreve o componente para o Backstage:
# catalog-info.yaml — na raiz do repositório do serviço
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: minha-api
title: API de Pedidos
description: |
API REST responsável pelo gerenciamento do ciclo de vida de pedidos,
incluindo criação, atualização de status, cancelamento e consultas.
# Etiquetas para filtragem no catálogo
tags:
- nodejs
- api
- postgres
- redis
# Links úteis para o time
links:
- url: https://grafana.empresa.com/d/minha-api
title: Dashboard Grafana
icon: dashboard
- url: https://minha-api.empresa.com/docs
title: Documentação da API
icon: docs
- url: https://empresa.pagerduty.com/service-directory/minha-api
title: PagerDuty
icon: alert
annotations:
# Integração com GitHub
github.com/project-slug: empresa/minha-api
# Integração com Kubernetes — mostra os pods no Backstage
backstage.io/kubernetes-id: minha-api
backstage.io/kubernetes-namespace: producao
# Integração com PagerDuty
pagerduty.com/service-id: PXXXXXX
# Integração com Sentry
sentry.io/project-slug: minha-api-producao
# Documentação técnica via TechDocs
backstage.io/techdocs-ref: dir:.
spec:
type: service
lifecycle: production # experimental | deprecated | production
owner: group:checkout-team
# Sistemas que este componente integra
system: sistema-pedidos
# Dependências declaradas
dependsOn:
- component:banco-usuarios
- resource:rds-producao
- resource:redis-producao
# APIs que este serviço provê e consome
providesApis:
- api-pedidos-v2
consumesApis:
- api-catalogo-v1
- api-pagamentos-v1
A API fornecida pelo serviço:
# api-definition.yaml
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: api-pedidos-v2
title: API de Pedidos v2
description: API REST para gestão de pedidos
tags:
- rest
- v2
spec:
type: openapi
lifecycle: production
owner: group:checkout-team
system: sistema-pedidos
definition:
$text: ./openapi.yaml # Referencia a spec OpenAPI do repositório
Templates de Scaffolding: Criando Novos Serviços
Os Software Templates do Backstage permitem criar novos serviços a partir de templates pré-configurados que já incluem as melhores práticas de segurança, observabilidade e CI/CD:
# templates/nodejs-api/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: nodejs-api-template
title: API Node.js com Express e PostgreSQL
description: |
Cria uma nova API Node.js com Express, PostgreSQL, Redis,
observabilidade com OpenTelemetry e pipeline de CI/CD completo.
tags:
- nodejs
- api
- postgres
- recommended
spec:
owner: group:plataforma
type: service
# Parâmetros coletados do desenvolvedor
parameters:
- title: Informações do Serviço
required: [nome, descricao, owner]
properties:
nome:
title: Nome do serviço
type: string
description: Nome em kebab-case (ex: api-pagamentos)
pattern: '^[a-z][a-z0-9-]*$'
ui:autofocus: true
descricao:
title: Descrição
type: string
description: Descreva brevemente a responsabilidade deste serviço
owner:
title: Time responsável
type: string
description: Time que será dono deste serviço
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
- title: Configuração de Infraestrutura
properties:
banco_dados:
title: Banco de dados PostgreSQL
type: boolean
default: true
redis:
title: Cache Redis
type: boolean
default: false
filas:
title: Filas SQS
type: boolean
default: false
ambiente_inicial:
title: Ambiente inicial
type: string
enum: [staging, production]
default: staging
- title: Repositório
required: [repo_url]
properties:
repo_url:
title: Localização do repositório
type: string
ui:field: RepoUrlPicker
ui:options:
allowedHosts: [github.com]
allowedOwners: [empresa]
# Passos de criação do serviço
steps:
- id: fetch-base
name: Baixa o template base
action: fetch:template
input:
url: ./skeleton
values:
nome: ${{ parameters.nome }}
descricao: ${{ parameters.descricao }}
owner: ${{ parameters.owner }}
banco_dados: ${{ parameters.banco_dados }}
redis: ${{ parameters.redis }}
filas: ${{ parameters.filas }}
- id: publish
name: Publica no GitHub
action: publish:github
input:
allowedHosts: [github.com]
description: ${{ parameters.descricao }}
repoUrl: ${{ parameters.repo_url }}
defaultBranch: main
repoVisibility: private
requireCodeOwnerReviews: true
requiredApprovingReviewCount: 1
topics:
- nodejs
- api
- ${{ parameters.owner }}
- id: provision-infra
name: Provisiona infraestrutura via Terraform
action: http:backstage:request
input:
method: POST
path: /api/terraform-runner/provision
body:
servico: ${{ parameters.nome }}
banco_dados: ${{ parameters.banco_dados }}
redis: ${{ parameters.redis }}
ambiente: ${{ parameters.ambiente_inicial }}
- id: register
name: Registra no catálogo
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
catalogInfoPath: /catalog-info.yaml
# O que mostrar ao desenvolvedor após a criação
output:
links:
- title: Repositório GitHub
url: ${{ steps.publish.output.remoteUrl }}
- title: Abrir no Catálogo
entityRef: ${{ steps.register.output.entityRef }}
text:
- title: Próximos passos
content: |
Seu novo serviço foi criado! 🎉
**O que foi configurado automaticamente:**
- Repositório GitHub com branch protection
- Pipeline de CI/CD (GitHub Actions)
- Infraestrutura AWS provisionada via Terraform
- Observabilidade com OpenTelemetry
- Registro no catálogo de serviços
**Próximos passos:**
1. Clone o repositório e explore a estrutura
2. Configure os secrets no repositório GitHub
3. Execute o primeiro deploy via pipeline
Skeleton do Template
O diretório skeleton contém a estrutura do serviço com placeholders substituídos pelo Backstage:
templates/nodejs-api/skeleton/
├── catalog-info.yaml
├── package.json
├── Dockerfile
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── deploy.yml
├── src/
│ ├── server.js
│ ├── health/
│ │ └── health.routes.js
│ └── observability/
│ └── tracing.js
├── terraform/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── docs/
│ ├── index.md
│ └── mkdocs.yml
└── ${{ values.nome }}.md # README gerado com o nome do serviço
Self-Service de Infraestrutura com Terraform e Crossplane
O self-service de infraestrutura permite que desenvolvedores provisionem recursos sem interagir com o time de plataforma. Duas abordagens complementares:
Módulos Terraform Aprovados
O time de plataforma publica módulos Terraform que encapsulam as melhores práticas e os desenvolvedores os consomem:
# Módulo publicado pelo time de plataforma
# terraform-registry.empresa.com/empresa/api-stack/aws
variable "nome_servico" {
description = "Nome do serviço — usado como prefixo de todos os recursos"
type = string
}
variable "ambiente" {
type = string
default = "staging"
}
variable "banco_dados" {
description = "Configuração do banco de dados"
type = object({
habilitado = bool
classe = optional(string, "db.t3.micro")
multi_az = optional(bool, false)
storage_gb = optional(number, 20)
})
default = { habilitado = false }
}
variable "redis" {
description = "Configuração do Redis"
type = object({
habilitado = bool
tipo_no = optional(string, "cache.t3.micro")
num_nos = optional(number, 1)
})
default = { habilitado = false }
}
# O módulo cria todos os recursos necessários para um serviço
module "banco" {
count = var.banco_dados.habilitado ? 1 : 0
source = "./modules/rds"
nome = "${var.nome_servico}-${var.ambiente}"
classe = var.banco_dados.classe
multi_az = var.ambiente == "production" ? true : var.banco_dados.multi_az
storage_gb = var.banco_dados.storage_gb
}
module "cache" {
count = var.redis.habilitado ? 1 : 0
source = "./modules/elasticache"
nome = "${var.nome_servico}-${var.ambiente}"
tipo_no = var.redis.tipo_no
num_nos = var.ambiente == "production" ? max(var.redis.num_nos, 2) : var.redis.num_nos
}
module "observabilidade" {
source = "./modules/observabilidade"
nome = var.nome_servico
}
# Outputs padronizados que o serviço consome
output "database_url_secret_arn" {
value = var.banco_dados.habilitado ? module.banco[0].secret_arn : null
}
output "redis_endpoint" {
value = var.redis.habilitado ? module.cache[0].endpoint : null
}
output "namespace_kubernetes" {
value = kubernetes_namespace.servico.metadata[0].name
}
Uso pelo time de produto — um arquivo platform.tf simples:
# Na raiz do repositório do serviço — minha-api/terraform/platform.tf
module "plataforma" {
source = "terraform-registry.empresa.com/empresa/api-stack/aws"
version = "~> 2.0"
nome_servico = "minha-api"
ambiente = var.environment
banco_dados = {
habilitado = true
classe = var.environment == "production" ? "db.r6g.large" : "db.t3.micro"
multi_az = var.environment == "production"
}
redis = {
habilitado = true
}
}
Crossplane: Infraestrutura como Recursos Kubernetes
O Crossplane é uma extensão do Kubernetes que permite criar recursos de cloud — RDS, S3, ElastiCache — usando o mesmo modelo declarativo de manifests YAML que os desenvolvedores já conhecem:
# kubernetes/infra/banco-dados.yaml
# O desenvolvedor declara o banco que precisa como um recurso Kubernetes
apiVersion: database.empresa.com/v1alpha1
kind: PostgreSQLDatabase
metadata:
name: minha-api-db
namespace: producao
spec:
# Classe de composição define o que "staging" vs "production" significa
compositionSelector:
matchLabels:
ambiente: production
parameters:
classe: db.r6g.large
storageGb: 100
multiAz: true
backupRetentionDays: 14
writeConnectionSecretToRef:
name: minha-api-db-credenciais
namespace: producao
O Crossplane recebe esse manifest, provisiona o RDS na AWS via Terraform ou AWS Provider, e armazena as credenciais como um Kubernetes Secret — tudo sem que o desenvolvedor precise interagir com o console da AWS ou escrever Terraform.
Métricas de Saúde da Plataforma
O time de plataforma usa métricas próprias para medir a eficácia da IDP:
DORA Metrics por time — mede se a plataforma está realmente acelerando a entrega dos times que a usam.
Tempo de onboarding — quanto tempo um novo desenvolvedor leva para fazer o primeiro deploy. O objetivo é reduzir de dias para horas.
Adoção de templates — percentual de novos serviços criados via templates vs criados manualmente. Alta adoção indica que os templates são úteis e de baixo atrito.
Toil reduction — redução do trabalho repetitivo e manual do time de plataforma medida em tickets, tempo de resposta e número de perguntas repetitivas.
# Painel de saúde da plataforma no Backstage
# plugins/platform-health/src/components/PlatformHealthCard.tsx
# Métricas exibidas no dashboard do time de plataforma:
# - Serviços no catálogo: 127
# - Serviços criados via template: 89% (últimos 6 meses)
# - Tempo médio de onboarding: 4 horas (meta: < 2h)
# - Tickets de infraestrutura/semana: 12 (baseline: 45)
# - DORA — Deploy Frequency: 8.2/dia (todos os times)
# - DORA — Lead Time: 42min (meta: < 60min)
# - DORA — Change Failure Rate: 1.8% (meta: < 5%)
# - DORA — MTTR: 18min (meta: < 60min)
Golden Paths: Opiniões, Não Mandatos
Um princípio fundamental de Platform Engineering é que os "paved roads" — caminhos pavimentados — devem ser a opção mais fácil, não a única. Um time com requisitos genuinamente diferentes deve poder sair do caminho padrão, desde que documente o motivo e aceite a responsabilidade adicional.
Essa filosofia se manifesta em como a plataforma é construída:
Os templates oferecem uma experiência de alta qualidade para o caso de uso mais comum — uma API Node.js com PostgreSQL e Redis, por exemplo. Times que precisam de Python, Go ou uma arquitetura radicalmente diferente podem criar seus próprios templates ou usar os módulos Terraform diretamente.
As restrições de segurança são aplicadas em camadas — algumas são mandatórias e aplicadas no nível de organização (criptografia em repouso, não expor portas desnecessárias ao mundo), outras são recomendações que os times podem sobrescrever com justificativa documentada.
O catálogo de serviços captura a realidade — não apenas o estado ideal. Serviços legados, serviços em modo experimental e serviços que deliberadamente saíram do caminho padrão são todos registrados, com suas exceções documentadas.
O Que Vem a Seguir
O próximo artigo cobre o penúltimo tema da série: Cultura e Práticas Organizacionais em DevOps — como medir a maturidade de uma organização DevOps, como conduzir revisões pós-incidente que geram aprendizado real e como construir uma cultura de melhoria contínua que sustenta as práticas técnicas ao longo do tempo.
Referências para Aprofundamento
Backstage - Backstage Documentation — backstage.io — Documentação oficial do Backstage cobrindo instalação, configuração do catálogo, software templates e desenvolvimento de plugins. - Backstage Plugins — backstage.io — Catálogo oficial de plugins do Backstage com integrações para GitHub, PagerDuty, Kubernetes, Grafana, SonarQube e centenas de outras ferramentas.
Platform Engineering - Team Topologies — teamtopologies.com — Livro fundamental sobre organização de times de tecnologia, introduzindo os conceitos de Stream-aligned teams, Platform teams e Enabling teams que embasam o Platform Engineering. - CNCF Platforms White Paper — tag-app-delivery.cncf.io — White paper oficial da CNCF sobre plataformas internas de desenvolvimento, cobrindo capacidades, modelo de maturidade e casos de uso.
Crossplane - Crossplane Documentation — docs.crossplane.io — Documentação completa do Crossplane com guias de instalação, criação de Composite Resource Definitions e integração com provedores AWS, Azure e GCP.