Há uma situação muito específica que justifica o uso do Bitbucket: a organização já tem o Jira como sistema central de rastreamento de trabalho, e a integração nativa entre repositório, pipeline e tickets é um requisito, não um diferencial. Nesse contexto, o Bitbucket não compete com o GitHub ou o GitLab pelo melhor repositório Git — ele compete sendo o repositório Git que melhor conversa com o restante do ecossistema Atlassian.
O Jira, o Confluence e o Bitbucket são os três produtos centrais do ecossistema Atlassian. A integração entre eles é profunda: um commit com a mensagem [PROJ-123] Fix null pointer exception in checkout automaticamente referencia e atualiza o ticket Jira PROJ-123. Um branch criado diretamente do Jira cria o branch no Bitbucket com o nome correto. O Confluence documenta os projetos e os links para repositórios e pull requests aparecem automaticamente nos espaços relevantes.
Para quem trabalha em uma organização onde o Jira é a fonte de verdade para o trabalho, ignorar essa integração é perder produtividade real.
Bitbucket Pipelines: CI/CD Nativo
O Bitbucket Pipelines é o sistema de CI/CD integrado ao Bitbucket Cloud — equivalente ao GitHub Actions e ao GitLab CI/CD. O arquivo de configuração é o bitbucket-pipelines.yml na raiz do repositório.
A sintaxe é mais simples do que o GitHub Actions ou o GitLab CI/CD, o que é tanto um ponto positivo (curva de aprendizado menor) quanto uma limitação (menos flexibilidade em cenários complexos):
# bitbucket-pipelines.yml
image: node:20-alpine # Imagem padrão para todos os steps
definitions:
# Serviços auxiliares (equivalente ao services: do GitLab)
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
redis:
image: redis:7-alpine
# Steps reutilizáveis (equivalente aos templates do GitLab)
steps:
- step: &instalar-dependencias
name: Instalar dependências
caches:
- node # Cache nativo do Bitbucket para node_modules
script:
- npm ci
- step: &testes
name: Testes unitários
caches:
- node
services:
- postgres
- redis
script:
- npm ci
- npm test -- --coverage
artifacts:
- coverage/**
pipelines:
# Pipeline para pull requests
pull-requests:
'**': # Qualquer branch com PR aberto
- step: *instalar-dependencias
- step: *testes
- step:
name: Lint e verificações de qualidade
caches:
- node
script:
- npm ci
- npm run lint
- npm run type-check
# Pipeline para branches específicas
branches:
main:
- step: *instalar-dependencias
- step: *testes
- step:
name: Build da imagem Docker
services:
- docker
script:
# Bitbucket tem variáveis automáticas similares ao GitLab
# $BITBUCKET_COMMIT, $BITBUCKET_BRANCH, $BITBUCKET_REPO_SLUG
- IMAGE_TAG=$BITBUCKET_DOCKER_REGISTRY/$BITBUCKET_REPO_SLUG:$BITBUCKET_COMMIT
- docker build -t $IMAGE_TAG .
- echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
- docker push $IMAGE_TAG
- step:
name: Deploy em Staging
deployment: staging # Liga ao Deployment Environment do Bitbucket
script:
- pipe: atlassian/aws-eks-kubectl-run:2.2.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
CLUSTER_NAME: 'meu-cluster-eks'
KUBECTL_COMMAND: >-
set image deployment/minha-app
app=$BITBUCKET_DOCKER_REGISTRY/$BITBUCKET_REPO_SLUG:$BITBUCKET_COMMIT
-n staging
- step:
name: Deploy em Produção
deployment: production
trigger: manual # Equivalente ao when: manual do GitLab
script:
- pipe: atlassian/aws-eks-kubectl-run:2.2.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
CLUSTER_NAME: 'meu-cluster-eks'
KUBECTL_COMMAND: >-
set image deployment/minha-app
app=$BITBUCKET_DOCKER_REGISTRY/$BITBUCKET_REPO_SLUG:$BITBUCKET_COMMIT
-n producao
# Pipelines agendados (cron)
custom:
testes-nocturnos:
- variables:
- name: AMBIENTE
default: staging
- step:
name: Testes de integração completos
caches:
- node
script:
- npm ci
- npm run test:integration -- --env=$AMBIENTE
Pipes: Ações Prontas do Bitbucket
Uma característica diferenciada do Bitbucket Pipelines são os Pipes — blocos de funcionalidade empacotados que simplificam integrações com serviços externos. É o equivalente das Actions do GitHub Marketplace, mas com foco no ecossistema Atlassian e parceiros cloud.
A Atlassian mantém pipes oficiais para AWS (deploy no ECS, EKS, S3, Lambda), Azure, GCP, Kubernetes e ferramentas de qualidade como Snyk e Sonarqube. Usar um pipe é mais simples do que escrever os comandos equivalentes:
# Usando o pipe da AWS para deploy no S3 (site estático)
- step:
name: Deploy no S3
script:
- npm run build
- pipe: atlassian/aws-s3-deploy:1.1.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
S3_BUCKET: 'minha-app-frontend'
LOCAL_PATH: 'dist'
DELETE_FLAG: 'true' # Remover arquivos deletados do bucket
CACHE_CONTROL: 'max-age=31536000'
# Pipe para invalidar CloudFront após o deploy
- step:
name: Invalidar cache do CloudFront
script:
- pipe: atlassian/aws-cloudfront-invalidate:0.6.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'sa-east-1'
DISTRIBUTION_ID: $CLOUDFRONT_DISTRIBUTION_ID
A Integração Jira-Bitbucket na Prática
A integração Jira-Bitbucket é automática quando os dois produtos pertencem à mesma organização no Atlassian Cloud. Não há configuração manual de webhooks ou tokens — a Atlassian gerencia a conexão internamente.
O que acontece na prática: quando um desenvolvedor cria um branch com o nome contendo o código do ticket Jira (por exemplo, feature/PROJ-456-autenticacao-oauth), o Jira automaticamente aparece no ticket PROJ-456 mostrando que há um branch ativo. Quando um commit é feito com PROJ-456 na mensagem, o Jira registra o commit no ticket. Quando o pull request é aberto, o ticket mostra o PR. Quando o PR é mergeado e o pipeline faz o deploy, o Jira pode ser configurado para transicionar automaticamente o ticket para "Em Produção".
Essa rastreabilidade bidirecional — do ticket ao código, do código ao deploy — sem configuração adicional é o principal argumento para o Bitbucket em organizações Jira-cêntricas.
Branch Permissions e Code Insights
O Bitbucket Cloud suporta Branch Permissions equivalentes às Branch Protection Rules do GitHub: exigir pull requests, número mínimo de aprovadores, impedir push direto na main, e exigir que o pipeline passe antes do merge.
O Code Insights é um recurso que permite que ferramentas externas de análise publiquem resultados diretamente no pull request — cobertura de testes, findings de segurança, análise estática. Sonarqube, Snyk e outras ferramentas têm integração nativa com o Code Insights do Bitbucket.
Bitbucket Data Center: Self-Hosted Enterprise
Assim como o GitLab tem a opção self-hosted, o Bitbucket tem o Bitbucket Data Center — a versão instalável em infraestrutura própria, com suporte a alta disponibilidade e clustering. É a escolha para organizações que precisam do ecossistema Atlassian mas não podem usar o cloud por razões regulatórias.
O Bitbucket Data Center pode ser instalado em servidores Linux próprios ou no Kubernetes via Helm Chart oficial da Atlassian. A configuração requer PostgreSQL externo, sistema de arquivos compartilhado (NFS ou equivalente para repositórios Git), Elasticsearch para busca e, opcionalmente, o Bamboo (CI/CD self-hosted da Atlassian) como alternativa ao Bitbucket Pipelines em ambientes on-premises.
Quando Não Usar o Bitbucket
O Bitbucket faz sentido em um contexto bem definido: organização que já usa Jira como ferramenta central e valoriza a integração nativa. Fora desse contexto, tanto o GitHub quanto o GitLab oferecem mais funcionalidades, comunidade maior e ecossistema de integrações mais rico.
O Bitbucket Pipelines, em particular, é menos poderoso do que o GitHub Actions (em termos de ecossistema de actions) e do que o GitLab CI/CD (em termos de funcionalidades enterprise como SAST integrado e templates avançados). Para times que não usam o Jira, a escolha entre GitHub e GitLab raramente passa pelo Bitbucket.
Referências para Aprofundamento
- Atlassian — Bitbucket: https://www.atlassian.com/software/bitbucket
Exercícios
Exercício 1
O pipeline define um step instalar-dependencias que roda npm ci, e em seguida os steps testes e lint — cada um rodando npm ci outra vez. Sabendo que no Bitbucket cada step executa em um container novo, o primeiro step serve para quê? E por que isso importa mais no Bitbucket do que em outras plataformas?
Ver resposta
✓ Resposta: Ele não serve para quase nada — apenas aquece o cache, e ainda assim de forma discutível.
Como cada step roda em um container próprio, o node_modules/ produzido pelo primeiro não existe nos seguintes. A única coisa que atravessa é o que for declarado como caches ou artifacts. Por isso os steps seguintes precisam mesmo reinstalar — o erro não é o npm ci repetido, é o step inicial existir. Ele adiciona um ciclo completo de provisionar container, baixar cache e instalar, sem entregar nada aos demais.
E há o mesmo problema visto no GitLab: o cache predefinido node do Bitbucket aponta para node_modules/, mas npm ci apaga esse diretório antes de instalar. O cache é restaurado e imediatamente destruído. O que valeria a pena cachear é ~/.npm, que o npm ci consulta para evitar baixar os tarballs.
Isso importa mais no Bitbucket por uma razão direta: a cobrança é por minuto de build, e os planos vêm com uma cota mensal — 500 ou 2500 minutos, conforme o plano. Cada execução inútil consome cota real. No GitHub Actions com repositórios privados o modelo é parecido; a diferença é que a cota do Bitbucket costuma ser mais apertada, e times descobrem o problema quando os pipelines param de rodar no dia 20 do mês.
A correção é remover o step e deixar cada um instalar o que precisa, com o cache apontando para o lugar certo:
definitions:
caches:
npm: ~/.npm # cache customizado, não o "node" predefinido
pipelines:
pull-requests:
'**':
- parallel: # testes e lint não dependem um do outro
- step: *testes
- step: *lint
O parallel é o ganho real: testes e lint são independentes e podem rodar ao mesmo tempo, encurtando o retorno ao desenvolvedor — que é o que o pipeline existe para dar.
Exercício 2
Todos os pipes de deploy recebem AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY como variáveis. Compare com o que os artigos anteriores usaram no GitHub Actions e no Azure DevOps. Qual o problema, e como o Bitbucket permite resolvê-lo?
Ver resposta
✓ Resposta: São credenciais estáticas de longa duração — um par de chaves de um usuário IAM, armazenado no Bitbucket, válido até alguém revogá-lo manualmente. É exatamente o padrão que o restante da série vinha eliminando.
O contraste é direto: o capstone autentica no GitHub Actions com id-token: write e role-to-assume, o Azure DevOps usa Workload Identity Federation, o EKS usa IRSA e o AKS usa Workload Identity. Todos esses trocam prova de identidade por um token temporário no momento do uso. Aqui, volta-se a guardar o segredo.
Os riscos concretos de uma chave estática em pipeline:
- Não expira. Vazada num log, num fork ou por um
postinstallmalicioso de dependência, ela vale indefinidamente. - Não tem escopo temporal. Um token OIDC dura minutos e é emitido para aquela execução; a chave serve a qualquer um, a qualquer hora, de qualquer lugar.
- Rotacionar dói. Como a mesma chave costuma ser reaproveitada em vários repositórios, trocá-la exige coordenação — e por isso ninguém troca.
O Bitbucket Pipelines oferece OIDC, e a mudança é pequena: configura-se o Bitbucket como provedor de identidade na AWS, cria-se uma role com trust policy restrita ao repositório, e o step passa a assumir a role sem nenhum segredo armazenado:
- step:
name: Deploy em Produção
deployment: production
oidc: true # habilita o token OIDC neste step
script:
- export AWS_ROLE_ARN=arn:aws:iam::123456789:role/bitbucket-deploy
- export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
- echo $BITBUCKET_STEP_OIDC_TOKEN > $AWS_WEB_IDENTITY_TOKEN_FILE
- pipe: atlassian/aws-eks-kubectl-run:2.2.0
variables:
AWS_OIDC_ROLE_ARN: $AWS_ROLE_ARN
# …
A trust policy na AWS restringe a confiança ao repositório e, se desejado, ao ambiente de deployment específico — de modo que um pipeline de outro repositório da mesma organização não consegue assumir a role de produção. É a mesma amarração por subject vista no IRSA e no Workload Identity.
Exercício 3
O step de produção tem trigger: manual. Na sexta-feira sobe um commit em main e ninguém aprova o deploy. Na segunda entram mais dois commits, cujos pipelines rodam normalmente. Na terça, alguém abre o pipeline de sexta e clica em aprovar. O que vai para produção?
Ver resposta
✓ Resposta: Vai para produção a versão de sexta-feira — o código de três dias atrás, sobrescrevendo qualquer coisa mais recente que já estivesse lá.
A causa está na tag da imagem: ela é fixada em $BITBUCKET_COMMIT, que é o SHA daquele pipeline. O step manual, quando finalmente executa, roda com todas as variáveis congeladas no momento em que o pipeline foi criado. Ele faz exatamente o que foi mandado fazer na sexta.
Isso caracteriza um rollback acidental, e é dos incidentes mais confusos de diagnosticar: não houve falha, ninguém errou comando, o pipeline ficou verde. Simplesmente uma funcionalidade que estava em produção desapareceu, e o histórico de deploys mostra uma implantação bem-sucedida no horário. Se as duas versões tiverem migrações de banco incompatíveis, o estrago passa do código para os dados.
O mesmo risco existe no when: manual do GitLab e nos environments com aprovação do GitHub. O que muda entre as plataformas é quanto elas ajudam a evitá-lo:
- O Bitbucket permite configurar, nos Deployment Environments, que apenas o pipeline mais recente possa ser implantado — bloqueando a promoção de execuções antigas.
- Definir uma expiração para aprovações pendentes: um deploy não aprovado em algumas horas deveria caducar, não ficar disponível por tempo indeterminado.
- Exibir no momento da aprovação o que está em produção agora e o que será implantado, para que a diferença seja visível a quem decide.
Vale notar a virtude oposta dessa mesma característica: como a imagem está fixada por SHA, o deploy é reprodutível — aprovar hoje entrega exatamente o artefato testado na sexta, e não um build novo com dependências que mudaram no meio do caminho. O problema não é fixar a versão; é não verificar se aquela versão ainda é a desejada.
Exercício 4
A seção custom: testes-nocturnos aparece sob o comentário "Pipelines agendados (cron)". Um time copia essa configuração esperando que os testes rodem toda madrugada. Eles rodam?
Ver resposta
✓ Resposta: Não. A palavra-chave custom: define um pipeline que só é disparado sob demanda — pela interface, pela API ou por um agendamento. Ela não contém nenhuma informação de horário, e o YAML sozinho não agenda coisa alguma.
O agendamento é criado fora do repositório, em Repository settings → Pipelines → Schedules, onde se escolhe o pipeline custom, a branch e a frequência. Sem esse passo, o bloco fica no arquivo, válido e inerte — e a única forma de executá-lo é alguém clicar em "Run pipeline" e escolhê-lo na lista.
O modo de falha é o silêncio: testes que nunca rodam não produzem alerta. Todo mundo acredita que existe uma suíte de integração cobrindo o sistema todas as noites, e meses depois descobre-se que ela nunca executou — geralmente quando um bug que ela pegaria chega a produção.
Há aqui uma diferença de filosofia que vale registrar, porque se repete em várias plataformas. No GitHub Actions e no GitLab, o agendamento pode ser declarado no próprio arquivo (on.schedule.cron e rules com $CI_PIPELINE_SOURCE == "schedule"), ficando versionado, revisável em pull request e visível no git log. No Bitbucket, ele vive na configuração do repositório — como os gates do Azure DevOps e os required reviewers do GitHub.
Configuração que mora fora do repositório tem sempre o mesmo conjunto de fraquezas: não passa por revisão, não tem histórico legível, não é replicada ao criar um repositório novo e pode ser alterada sem deixar rastro óbvio. Quando isso for inevitável, a mitigação é documentar no README quais configurações externas o repositório espera — e, idealmente, verificá-las por API em uma rotina de auditoria.
Repare que o bloco também declara variables com AMBIENTE e valor padrão staging: esse é o mecanismo que permite pedir o parâmetro a quem dispara o pipeline manualmente — mais um indício de que custom: foi desenhado para execução sob demanda, e não para cron.
Exercício 5
O artigo apresenta a integração Jira-Bitbucket como o principal argumento a favor da plataforma: branch com PROJ-456 no nome aparece no ticket, commit citando o código é registrado, e o ticket pode transicionar automaticamente para "Em Produção". Onde essa rastreabilidade é frágil?
Ver resposta
✓ Resposta: Toda ela depende de uma string digitada à mão. A integração não conhece a relação entre código e ticket — ela procura um padrão de texto no nome do branch e na mensagem do commit.
Isso quebra de maneiras banais e frequentes: um branch nomeado fix/checkout-null-pointer sem o código do ticket não aparece em lugar nenhum; um PROJ456 sem hífen não casa; um commit de correção rápida feito direto na interface, sem citar o ticket, some do histórico. E ninguém é avisado — o ticket simplesmente fica com menos informação do que deveria, e o time confia num histórico incompleto sem saber.
Há um risco maior no sentido inverso, com os smart commits. A mesma varredura de texto interpreta comandos na mensagem:
git commit -m "PROJ-456 #time 2h #comment revisado com o time #done"
Isso lança horas, adiciona comentário e fecha o ticket. Um #done escrito por engano — ou dentro de uma mensagem de merge que agrega commits de vários tickets — fecha tickets sem que ninguém tenha decidido fechá-los. E mensagem de commit é imutável na prática: não dá para "desfazer" o gatilho, só corrigir o estado no Jira depois.
As mitigações são de processo, e valem em qualquer combinação de ferramentas:
- Criar o branch a partir do Jira, e não do terminal. O nome sai correto por construção, que é o benefício real da integração.
- Validar no CI que a mensagem de commit ou o título do PR contém um código de ticket válido — transformando a convenção em regra verificável.
- Ancorar a transição de estado no deploy, e não na mensagem: quando o pipeline de produção conclui, ele chama a API do Jira com o ticket ligado àquele commit. O que move o ticket passa a ser um fato observado, não um texto.
Vale a comparação honesta com o exercício sobre o catálogo do Backstage: lá, o grafo de dependências apodrecia porque era declarado à mão e nunca verificado. É o mesmo padrão aqui — metadado mantido por disciplina humana degrada em silêncio, e a defesa é sempre a mesma: derivá-lo de algo que o sistema já sabe, ou validá-lo automaticamente.