Como usar BuildKit e cache remoto para acelerar builds Docker em pipelines CI

Docker & Containers · Por · Publicado em · Atualizado em · 9 min de leitura

Três grandes dutos metálicos paralelos atravessando uma área com árvores
Metáfora visual para pipelines de CI e fluxo de build Foto: Wolfgang Weiser / Pexels

Este guia mostra como usar BuildKit e cache remoto para acelerar builds Docker em pipelines CI. Você vai habilitar o Buildx, preparar o Dockerfile para aproveitar cache de dependências e configurar cache remoto via registry em provedores de CI. O objetivo é reduzir tempo de execução mantendo reprodutibilidade e segurança.

Pré-requisitos e visão geral do BuildKit no CI

BuildKit é o mecanismo de build do Docker com paralelismo, controle fino de cache e recursos como mounts de cache e segredos. Em runners efêmeros de CI, o cache local se perde. Por isso, usamos cache remoto para reaproveitar camadas entre execuções.

Você precisa ter o Docker recente com Buildx. Se necessário, veja como instalar em Linux em Instale o Docker e o Compose (/como-instalar-docker-docker-compose-linux). Se usa multistage, este guia complementa Como criar imagens Docker leves: multistage (/como-criar-imagens-docker-leves-multistage-minimizar-camadas).

O cache remoto via registry armazena metadados e camadas como artefatos OCI. Ele funciona em qualquer CI que consiga efetuar login e push em um registry. Não armazene segredos no cache; use mounts de segredo do BuildKit quando precisar.

  • Conta em um registry com permissão de push/pull.
  • Runner de CI com Docker disponível (com ou sem DinD).
  • Dockerfile compatível com BuildKit e, de preferência, multistage.
  • Acesso de rede do CI ao registry de imagens.

1) Habilitar BuildKit e Buildx localmente

Ative o BuildKit e o Buildx para testar o fluxo localmente antes de levar ao CI. Crie um builder dedicado e faça o bootstrap do driver. Isso garante que você veja os mesmos logs e comportamento que no pipeline.

Depois, rode um build simples e confirme que o Buildx está em uso. Use o modo plain para ver hits de cache claramente nos logs.

# Ativar BuildKit na sessão atual
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1

docker buildx version

# Criar e usar um builder baseado em container
docker buildx create --name ci-builder --use
# Inicializar capacidades (QEMU, cache driver, etc.)
docker buildx inspect --bootstrap

# Build de teste com saída detalhada
docker buildx build --progress=plain -t test-image .

2) Preparar o Dockerfile para aproveitar o cache (cache mounts)

Para usufruir do cache remoto, reduza invalidações e use mounts de cache nas etapas que baixam dependências. Com BuildKit, o comando RUN aceita --mount=type=cache para persistir diretórios como caches entre builds.

Inclua a diretiva de sintaxe do Dockerfile para versões recentes e isole dependências em estágios. Evite copiar o repositório inteiro antes do install. Nunca grave segredos em camadas; use secret mounts quando necessário.

  • Use COPY seletivo dos manifests antes do install.
  • Separe deps de build e de produção.
  • Evite invalidações colocando arquivos que mudam pouco primeiro.
  • Ative --mount=type=cache para gerenciadores de pacotes.
# syntax=docker/dockerfile:1.6
FROM node:20-slim AS base
WORKDIR /app

# 1) Dependências para build (usa cache do npm)
FROM base AS deps
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# 2) Build do app
FROM base AS build
COPY --from=deps /app/node_modules /app/node_modules
COPY . .
RUN --mount=type=cache,target=/root/.npm \
    npm run build

# 3) Dependências de produção (sem dev)
FROM base AS prod-deps
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev

# 4) Runtime enxuto
FROM node:20-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
CMD ["node", "dist/index.js"]

3) Configurar cache remoto via registry com Buildx

Em runners efêmeros, use cache remoto baseado em registry. O Buildx pode exportar e importar cache com flags --cache-to e --cache-from. A referência aponta para uma tag reservada do cache.

Autentique no registry antes do build. Use mode=max para gravar o máximo de metadados possíveis e melhorar hits. Garanta que a tag do cache seja estável para ser reutilizada por todos os builds.

# Variáveis de exemplo (ajuste para seu registry)
export REGISTRY=registry.example.com
export IMAGE_NAME=myapp
export IMAGE_TAG=sha-$(git rev-parse --short HEAD)
export IMAGE=$REGISTRY/$IMAGE_NAME:$IMAGE_TAG
export CACHE_REF=$REGISTRY/$IMAGE_NAME:buildcache

# Login no registry
echo "$REG_PASS" | docker login "$REGISTRY" -u "$REG_USER" --password-stdin

# Build e push com cache remoto
docker buildx build \
  --builder ci-builder \
  --progress=plain \
  --cache-from=type=registry,ref=$CACHE_REF \
  --cache-to=type=registry,ref=$CACHE_REF,mode=max \
  -t $IMAGE \
  --push \
  .

4) Exemplo no GitHub Actions com cache de registry

No GitHub Actions, use ações para configurar o Buildx, autenticar no registry e rodar o build com cache-from/to. A tag de cache fica fixa e a de imagem pode usar o SHA do commit.

Se você ainda não tem um pipeline base, veja o passo a passo em Como configurar pipeline de deploy automático (/como-configurar-pipeline-de-deploy-automatico-github-actions) e, para exemplos em PHP, CI/CD com GitHub Actions: guia PHP/Laravel (/guia-cicd-github-actions-php-laravel).

name: docker-build
on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - name: Login registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push with registry cache
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:sha-${{ github.sha }}
          cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache
          cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=max

5) Exemplo no GitLab CI (ou CI com Docker-in-Docker)

Em pipelines com Docker-in-Docker, habilite o serviço dind, desative TLS no runner quando apropriado e use o Buildx para exportar e importar o cache no registry do projeto.

A autenticação usa variáveis seguras do CI. Cuidado ao expor logs: não imprima tokens. Mantenha a referência de cache estável para maximizar o reaproveitamento entre branches.

stages: [build]

variables:
  DOCKER_TLS_CERTDIR: ""
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2

services:
  - name: docker:24.0-dind

build-image:
  image: docker:24.0
  stage: build
  script:
    - apk add --no-cache curl
    - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
    - docker buildx create --use --name ci-builder
    - docker buildx build --progress=plain \
        --cache-from=type=registry,ref=$CI_REGISTRY_IMAGE:buildcache \
        --cache-to=type=registry,ref=$CI_REGISTRY_IMAGE:buildcache,mode=max \
        -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA \
        --push \
        .
  only:
    - main

6) Como confirmar que o cache remoto está funcionando

Rode um build para aquecer o cache e depois repita mudando apenas arquivos não críticos. Você deve ver "CACHED" ou etapas puladas rapidamente nos logs com --progress=plain.

Também confira o tempo total e o tamanho da transferência. Se usar builders diferentes, aponte explicitamente com --builder para não criar caches paralelos desnecessários.

  • Use --progress=plain para logs detalhados.
  • Valide que o cache-ref aponta para a tag correta.
  • Confirme o login no registry antes do build.
# 1) Aquecer cache
docker buildx build --builder ci-builder \
  --cache-to=type=registry,ref=$CACHE_REF,mode=max \
  -t $IMAGE --push --progress=plain .

# 2) Segunda execução deve mostrar hits de cache
docker buildx build --builder ci-builder \
  --cache-from=type=registry,ref=$CACHE_REF \
  -t $IMAGE --push --progress=plain .

7) Problemas comuns e como resolver

Cache não bate em install de dependências? Verifique se arquivos de manifesto são copiados antes do restante do código. Para npm, package-lock.json precisa ser estável.

Mudanças em ARG ou ENV no topo do Dockerfile invalidam camadas seguintes. Fixe versões e ordene instruções por estabilidade.

Em runners que limpam tudo, o cache remoto é essencial. docker buildx prune remove caches; rode-o somente quando necessário e com parâmetros adequados para preservar espaço.

  • Organize o Dockerfile por estabilidade das camadas.
  • Fixe versões de base e ferramentas.
  • Evite ARGs dinâmicos no topo.
  • Mantenha uma única referência de cache por serviço.
# Limpeza de caches (use com cautela)
# Isso apaga caches locais do builder selecionado
# Confirme o builder atual com: docker buildx ls

docker buildx prune --all --force

Perguntas frequentes

BuildKit e Buildx são a mesma coisa?

Não. BuildKit é o mecanismo de build. Buildx é a interface de linha de comando que expõe recursos avançados do BuildKit, como multi‑plataforma, drivers e cache remoto. Você usa o Buildx para acionar o BuildKit com flags como --cache-from e --cache-to.

Quando usar cache remoto em vez do cache local?

Em pipelines CI com runners efêmeros, o cache local se perde a cada execução. O cache remoto via registry permite reaproveitar camadas entre jobs e branches, reduzindo tempo de build e tráfego. Em ambientes de desenvolvimento locais, o cache local costuma ser suficiente.

O cache remoto armazena segredos do build?

Não, desde que você não escreva segredos em camadas. Use mounts de segredo do BuildKit para passar tokens em tempo de build sem persistí‑los na imagem ou no cache. Evite ecoar segredos no console do CI para não registrá‑los nos logs.

Como lidar com monorepo com vários serviços?

Crie uma referência de cache por serviço, usando uma tag de cache estável por pasta. Isso evita colisões entre projetos e maximiza o reaproveitamento. Padronize Dockerfiles com multistage e mounts de cache, e compartilhe um template de pipeline entre serviços.

Por que meus installs continuam sem cache mesmo com cache-to/from?

Geralmente há invalidação por ordem das instruções ou manifests instáveis. Copie apenas package*.json ou o arquivo de lock antes do restante do código, fixe versões, e use --mount=type=cache no gerenciador de pacotes. Verifique os logs com --progress=plain para localizar o primeiro miss.

Preciso publicar a imagem final para usar cache remoto?

Não. Você pode exportar apenas o cache para o registry, sem publicar uma tag pública de imagem final. Entretanto, a maioria dos pipelines já faz push da imagem; nesse caso, aproveite a mesma autenticação para enviar e consumir o cache.

Leve o cache remoto ao seu CI hoje

Implemente BuildKit e cache remoto no seu pipeline e compare o tempo de build. Depois, complemente com multistage e boas práticas de Dockerfile.

Explorar mais guias

Veja também