GitLab Runner: Docker-images bouwen

Bouw en push Docker-images efficiënt vanuit uw GitLab CI/CD-pipelines met Stackhero runners en Docker-in-Docker

👋 Welkom bij de Stackhero-documentatie!

Stackhero biedt een eenvoudige GitLab Runner cloud oplossing waarmee u uw GitLab CI/CD-jobs efficiënt en zonder gedoe kunt uitvoeren. Dit kunt u verwachten:

  • Onbeperkte CI/CD-minuten: voer uw pijplijnen zo vaak uit als nodig, zonder kosten per minuut of onverwachte extra kosten.
  • Meerdere gelijktijdige jobs: versnel uw ontwikkeling door verschillende jobs parallel uit te voeren.
  • De Docker executor met ondersteuning voor Docker-in-Docker: bouw en push eenvoudig container images als onderdeel van uw CI/CD-proces.
  • Werkt naadloos met zowel GitLab.com als self-managed GitLab-omgevingen.
  • Een privé, dedicated infrastructuur met snelle NVMe/SSD-opslag zorgt voor stabiele en voorspelbare build-prestaties.
  • Beschikbaar in de regio's 🇪🇺 Europa en 🇺🇸 USA om aan de behoeften van uw team te voldoen.

Bespaar tijd: u kunt uw eerste GitLab Runner koppelen en binnen enkele minuten starten met het uitvoeren van pijplijnen!

Met een Stackhero GitLab Runner draait elke job in een verse container via de Docker executor. U kunt uw eigen Docker-images direct in uw pipeline bouwen door Docker-in-Docker (DinD) in te schakelen. Deze configuratie start een Docker-daemon naast uw job, zodat u docker build- en docker push-commando's kunt uitvoeren als onderdeel van uw CI/CD-proces.

Elke run profiteert van onbeperkte CI/CD-minuten: u kunt zo vaak bouwen als nodig is, zonder u zorgen te maken over limieten. Uw build-cache wordt opgeslagen op de dedicated schijf van de runner, waardoor herhaalde builds eerdere lagen kunnen hergebruiken. Dit verkort de buildtijden aanzienlijk en versnelt uw pipelines.

U kunt het volgende voorbeeld van .gitlab-ci.yml toevoegen aan uw repository. Deze configuratie bouwt de Dockerfile in de root van uw project:

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:
    # Vervang "my-image" door de gewenste naam:
    - docker build -t my-image .
    # Optioneel: voer een snelle test uit op het gebouwde image:
    # - docker run --rm my-image /path/to/tests

In dit voorbeeld gebruiken we Docker-image versie 29. U kunt een nieuwere versie gebruiken zodra deze beschikbaar is. De laatste tags vindt u op de officiële Docker image pagina.

In deze setup start de docker:29-dind service een Docker-daemon naast uw job, en DOCKER_HOST: "tcp://docker:2375" vertelt de docker CLI om deze te gebruiken. Stel altijd DOCKER_HOST in wanneer u een docker:dind service gebruikt: zonder deze variabele maakt de CLI stilzwijgend verbinding met een andere daemon, waardoor uw build kan slagen zelfs als de service verkeerd is geconfigureerd. Dit verbergt echte problemen (en breekt tools zoals Testcontainers die afhankelijk zijn van de service). DOCKER_TLS_CERTDIR: "" zorgt ervoor dat er verbinding wordt gemaakt via de niet-versleutelde poort 2375 op het interne jobnetwerk.

Als eenvoudiger alternatief kunt u het services-blok en beide variabelen weglaten: de Stackhero runner biedt ook een kant-en-klare Docker-daemon via een gemounte socket, zodat een eenvoudige docker build werkt zonder extra configuratie.

Testcontainers is een testbibliotheek, beschikbaar voor Java, Go, Node.js, Python, .NET en andere talen, die echte services als tijdelijke Docker-containers opstart tijdens het uitvoeren van uw tests. In plaats van bijvoorbeeld een database of message broker te mocken, communiceren uw integratietests met een echte PostgreSQL-, MySQL-, Redis- of Kafka-instantie, die vóór de tests wordt aangemaakt en direct erna weer wordt verwijderd. Het is vooral populair in Java- en Spring-projecten.

Testcontainers heeft een Docker-daemon nodig en werkt dus met exact dezelfde Docker-in-Docker-configuratie als hierboven. Er is geen extra variabele nodig:

test:
  stage: test
  # Gebruik het image dat uw tests nodig hebben (hier een JDK), niet per se het docker-image:
  image: gradle:jdk21
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  script:
    - gradle test

Testcontainers leest DOCKER_HOST om de daemon te vinden en gebruikt dezelfde docker hostname om de poorten te bereiken die door de containers worden gepubliceerd. Beide werken zelfstandig, omdat het docker alias op het netwerk van uw job wordt opgelost.

Houd DOCKER_HOST in uw Testcontainers-jobs. Zonder deze variabele valt Testcontainers terug op de Docker-socket die door de runner is gemount, en probeert vervolgens uw testcontainers te bereiken via het host-IP-adres, waar uw job geen verbinding mee kan maken. Het gebruikelijke symptoom is dat de Ryuk-helpercontainer faalt met Wait strategy failed. Container is removed, of Timed out waiting for log output matching '.*Started.*'.

GitLab stelt vooraf gedefinieerde variabelen beschikbaar (CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, CI_REGISTRY_IMAGE) zodat uw pipeline veilig kan authenticeren en images kan pushen naar de container registry van uw project. Er zijn geen extra secrets nodig.

Hier is een voorbeeldjob die uw image bouwt en pusht:

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"
    # Als u op de default branch zit, kunt u ook "latest" taggen en pushen:
    - |
      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

Om uw images naar een andere registry te pushen (zoals Docker Hub of een private registry), kunt u credentials opslaan als CI/CD-variabelen en deze op dezelfde manier gebruiken met docker login.

De schijf van uw runner blijft behouden tussen pipelines, waardoor u imagelagen als build-cache kunt hergebruiken. Dit maakt herhaalde builds veel sneller. Hier is een voorbeeldconfiguratie:

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:
    # Pull het laatste image om de cache te vullen (indien aanwezig):
    - 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"

Met deze aanpak kunnen uw builds gebruikmaken van Docker's laagcaching, zodat alleen nieuwe of gewijzigde lagen opnieuw worden opgebouwd.

Uw abonnement bepaalt hoeveel jobs er gelijktijdig kunnen draaien. Jobs in dezelfde stage starten samen, tot aan uw limiet voor gelijktijdigheid. Dit betekent dat meerdere onafhankelijke jobs parallel kunnen draaien en afronden zodra de langzaamste klaar is, in plaats van te wachten tot elke job na elkaar klaar is.

Voorbeeld:

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

Als u uw gelijktijdigheid op 1 of hoger instelt, zullen de unit, integration en e2e jobs tegelijkertijd worden uitgevoerd.

Voor meer informatie over het bouwen van Docker-images in GitLab CI/CD-pipelines kunt u de officiële GitLab-documentatie over Docker builds raadplegen.