Bitbucket e o Ecossistema Atlassian

[327] Bitbucket e o Ecossistema Atlassian

A plataforma que não compete pelo melhor repositório Git, e sim por conversar melhor com o resto do ecossistema Atlassian: o bitbucket-pipelines.yml e sua sintaxe enxuta, os Pipes prontos para AWS e Kubernetes, a integração automática com o Jira, o Data Center self-hosted e quando não vale a pena adotá-lo.
DevOps

17 min de leitura

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

Exercícios

Exercício 1

O pipeline define um step instalar-dependencias que roda npm ci, e em seguida os steps testes e lintcada 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 postinstall malicioso 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.

Comentários

Mais em DevOps

Boas Práticas de Imagens: Leveza, Segurança e Camadas
Boas Práticas de Imagens: Leveza, Segurança e Camadas

Os princípios que separam uma imagem funcional de uma imagem de produção…

Introdução ao Terraform: Infraestrutura que Você Pode Versionar
Introdução ao Terraform: Infraestrutura que Você Pode Versionar

O problema dos snowflake servers e a infraestrutura que se pode versionar: o…

Introdução ao Kubernetes: Orquestrando Containers em Escala
Introdução ao Kubernetes: Orquestrando Containers em Escala

A orquestração de containers além de uma única máquina: os componentes do…