GitLab Runner: Building Docker images

Build and push Docker images efficiently from your GitLab CI/CD pipelines using Stackhero runners and Docker-in-Docker

👋 Welcome to the Stackhero documentation!

Stackhero offers you an easy-to-use GitLab Runner cloud solution, designed to efficiently run your GitLab CI/CD jobs. Here’s what you can expect:

  • Unlimited CI/CD minutes: there’s no per-minute billing, so your pipelines can run whenever you need them.
  • Concurrent jobs: run multiple jobs in parallel to speed up your entire pipeline.
  • The Docker executor with Docker-in-Docker support: simplify building and pushing your container images.
  • Compatible with GitLab.com as well as any self-managed GitLab instance.
  • A private, dedicated VM powered by fast NVMe/SSD disks for consistent, reliable builds.
  • Available in both 🇪🇺 Europe and 🇺🇸 USA regions.

Save time: connect your first GitLab Runner and start running pipelines in just a few minutes!

With a Stackhero GitLab Runner, each job runs in an isolated container using the Docker executor. You can build your own Docker images directly in your pipeline by enabling Docker-in-Docker (DinD). This setup launches a Docker daemon alongside your job, allowing you to run docker build and docker push commands as part of your CI/CD process.

Every run benefits from unlimited CI/CD minutes: you can build as often as you need, without worrying about quotas. Your build cache is stored on the runner's dedicated disk, which means repeated builds can reuse previous layers. This significantly reduces build times and speeds up your pipelines.

You can add the following sample .gitlab-ci.yml to your repository. This configuration builds the Dockerfile located at the root of your project:

build-image:
  stage: build
  image: docker:29
  services:
    - docker:29-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker info
  script:
    # Replace "my-image" with your desired name:
    - docker build -t my-image .
    # Optionally, run a quick test on the built image:
    # - docker run --rm my-image /path/to/tests

We are using Docker image version 29 in this example. You may want to use a newer version if needed. You can find the latest tags on the official Docker image page.

In this setup, the docker:29-dind service starts the Docker daemon. Setting DOCKER_TLS_CERTDIR: "/certs" enables a secure TLS connection between your job and the Docker daemon.

GitLab provides predefined variables (CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, CI_REGISTRY_IMAGE) so your pipeline can authenticate and push images to your project's container registry securely. No additional secrets are required.

Here is an example job that builds and pushes your image:

build-and-push:
  stage: build
  image: docker:29
  services:
    - docker:29-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  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"
    # If you are on the default branch, you can also tag and push "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

To push your images to another registry (such as Docker Hub or a private registry), you can store credentials as CI/CD variables and use them with docker login in the same way.

Your runner's disk is preserved between pipelines, allowing you to reuse image layers as a build cache. This can make repeated builds much faster. Here is an example configuration:

build-cached:
  stage: build
  image: docker:29
  services:
    - docker:29-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    # Pull the latest image to seed the cache (if it exists):
    - 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"

This method allows your builds to take advantage of Docker's layer caching, so only new or modified layers are rebuilt.

Your plan determines how many jobs can run at the same time. Jobs within the same stage start together, up to your concurrency limit. This means several independent jobs can run in parallel and finish as soon as the slowest one completes, without waiting for each job to finish sequentially.

Example:

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

If you set your concurrency to 1 or higher, the unit, integration, and e2e jobs will run at the same time.

For more information on building Docker images in GitLab CI/CD pipelines, you can refer to the official GitLab documentation on using Docker builds.