GitLab Runner: Construir imagens Docker

Construa e envie imagens Docker de forma eficiente a partir dos seus pipelines GitLab CI/CD utilizando runners Stackhero e Docker-in-Docker

👋 Bem-vindo à documentação da Stackhero!

A Stackhero disponibiliza uma solução GitLab Runner cloud simples que torna a execução dos seus jobs GitLab CI/CD eficiente e sem complicações. Eis o que pode esperar:

  • Minutos CI/CD ilimitados: execute os seus pipelines sempre que precisar, sem cobrança por minuto nem custos inesperados.
  • Vários jobs em simultâneo: acelere o desenvolvimento ao executar múltiplos jobs em paralelo.
  • O executor Docker com suporte para Docker-in-Docker: construa e faça push de imagens de containers facilmente como parte do seu processo CI/CD.
  • Compatível tanto com GitLab.com como com instâncias GitLab self-managed.
  • Uma infraestrutura privada e dedicada com armazenamento NVMe/SSD rápido garante um desempenho de build estável e previsível.
  • Disponível nas regiões 🇪🇺 Europa e 🇺🇸 USA para responder às necessidades da sua equipa.

Poupe tempo: pode ligar o seu primeiro GitLab Runner e começar a executar pipelines em apenas alguns minutos!

Com um GitLab Runner da Stackhero, cada job é executado dentro de um novo contentor utilizando o executor Docker. Pode construir as suas próprias imagens Docker diretamente no pipeline ao ativar o Docker-in-Docker (DinD). Esta configuração inicia um daemon Docker ao lado do seu job, permitindo-lhe executar comandos docker build e docker push como parte do seu processo CI/CD.

Cada execução beneficia de minutos CI/CD ilimitados: pode construir quantas vezes forem necessárias, sem preocupações com limites de utilização. O cache de build é armazenado no disco dedicado do runner, o que significa que builds repetidos podem reutilizar camadas anteriores. Isto reduz significativamente o tempo de build e acelera a conclusão dos seus pipelines.

Pode adicionar o seguinte exemplo de .gitlab-ci.yml ao seu repositório. Esta configuração constrói o Dockerfile na raiz do seu projeto:

build-image:
  stage: build
  image: docker:29
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker info
  script:
    # Substitua "my-image" pelo nome pretendido:
    - docker build -t my-image .
    # Opcionalmente, execute um teste rápido na imagem construída:
    # - docker run --rm my-image /path/to/tests

Estamos a utilizar a versão 29 da imagem Docker neste exemplo. Pode optar por uma versão mais recente assim que estiver disponível. Pode consultar as tags mais recentes na página oficial da imagem Docker.

Nesta configuração, o serviço docker:29-dind inicia um daemon Docker ao lado do seu job, e DOCKER_HOST: "tcp://docker:2375" indica ao CLI do docker para o utilizar. Defina sempre o DOCKER_HOST quando declarar um serviço docker:dind: sem esta variável, o CLI liga-se silenciosamente a outro daemon, o que pode fazer com que o build passe mesmo quando o serviço está mal configurado, ocultando problemas reais (e quebrando ferramentas como o Testcontainers que dependem deste serviço). DOCKER_TLS_CERTDIR: "" liga-se através da porta interna não segura (sem TLS) 2375 na rede interna do job.

Como alternativa mais simples, pode omitir o bloco services e ambas as variáveis: o runner da Stackhero também expõe um daemon Docker pronto a usar através de um socket montado, pelo que um simples docker build funciona sem configuração adicional.

O Testcontainers é uma biblioteca de testes, disponível para Java, Go, Node.js, Python, .NET e outras linguagens, que inicia serviços reais como contentores Docker descartáveis durante a execução dos seus testes. Em vez de simular uma base de dados ou um message broker, os seus testes de integração comunicam com uma instância real de PostgreSQL, MySQL, Redis ou Kafka, criada antes dos testes e removida logo após. É especialmente popular em projetos Java e Spring.

O Testcontainers necessita de um daemon Docker, pelo que funciona exatamente com a mesma configuração Docker-in-Docker apresentada acima. Não é necessária nenhuma variável adicional:

test:
  stage: test
  # Utilize a imagem necessária para os seus testes (um JDK neste caso), não necessariamente a imagem docker:
  image: gradle:jdk21
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  script:
    - gradle test

O Testcontainers lê a variável DOCKER_HOST para encontrar o daemon e reutiliza o mesmo hostname docker para aceder às portas publicadas pelos contentores que inicia. Ambos funcionam de forma autónoma, pois o alias docker é resolvido na rede do seu job.

Mantenha o DOCKER_HOST nos seus jobs com Testcontainers. Sem esta variável, o Testcontainers recorre ao socket Docker montado pelo runner e tenta aceder aos seus contentores de teste através do endereço IP do host, ao qual o seu job não consegue ligar-se. O sintoma mais comum é o contentor auxiliar Ryuk falhar com Wait strategy failed. Container is removed, ou Timed out waiting for log output matching '.*Started.*'.

O GitLab disponibiliza variáveis predefinidas (CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, CI_REGISTRY_IMAGE) para que o seu pipeline possa autenticar-se e enviar imagens para o registo de contentores do seu projeto de forma segura. Não são necessários segredos adicionais.

Segue um exemplo de job que constrói e envia a sua imagem:

build-and-push:
  stage: build
  image: docker:29
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
    # Se estiver na branch principal, pode também marcar e enviar "latest":
    - |
      if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
        docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:latest"
        docker push "$CI_REGISTRY_IMAGE:latest"
      fi

Para enviar as suas imagens para outro registo (como o Docker Hub ou um registo privado), pode guardar as credenciais como variáveis CI/CD e utilizá-las com o docker login da mesma forma.

O disco do seu runner é persistente entre pipelines, permitindo reutilizar camadas de imagem como cache de build. Isto pode tornar builds repetidos muito mais rápidos. Eis um exemplo de configuração:

build-cached:
  stage: build
  image: docker:29
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    # Faça pull da imagem mais recente para alimentar o cache (se existir):
    - docker pull "$CI_REGISTRY_IMAGE:latest" || true
    - docker build --cache-from "$CI_REGISTRY_IMAGE:latest" -t "$CI_REGISTRY_IMAGE:latest" .
    - docker push "$CI_REGISTRY_IMAGE:latest"

Este método permite que os seus builds tirem partido do cache de camadas do Docker, reconstruindo apenas as camadas novas ou alteradas.

O seu plano determina quantos jobs podem ser executados em simultâneo. Jobs na mesma etapa iniciam ao mesmo tempo, até ao limite de concorrência definido. Isto significa que vários jobs independentes podem correr em paralelo, terminando assim que o mais lento terminar, em vez de esperar que cada um termine sequencialmente.

Exemplo:

stages:
  - test

unit:
  stage: test
  image: node:22
  script: npm run test:unit

integration:
  stage: test
  image: node:22
  script: npm run test:integration

e2e:
  stage: test
  image: node:22
  script: npm run test:e2e

Se definir a sua concorrência para 1 ou superior, os jobs unit, integration e e2e serão executados em simultâneo.

Para mais detalhes sobre como construir imagens Docker em pipelines GitLab CI/CD, consulte a documentação oficial do GitLab sobre builds Docker.