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 benefit from:
- 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 reliable, consistent builds.
- Available in both 🇪🇺 Europe and 🇺🇸 USA regions.
Save time: connect your first GitLab Runner and start running pipelines in just a few minutes!
Introduction
With a Stackhero GitLab Runner, each job runs inside an isolated container using the Docker executor. You can build your own Docker images directly within your pipeline by enabling Docker-in-Docker (DinD). This configuration starts 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 are free to trigger as many builds as you need, without worrying about quotas. Your build cache is stored on the runner's dedicated disk, enabling reuse of previous layers for repeated builds. This significantly reduces build times and speeds up your pipelines.
Building a Docker image with Docker-in-Docker
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 wish to use a newer version as they become available. 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.
Pushing to the GitLab container registry
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.
Speeding up repeat builds
Your runner's disk persists 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 approach allows your builds to benefit from Docker's layer caching, so only new or modified layers are rebuilt.
Running jobs in parallel
Your plan determines how many jobs can run concurrently. 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 job completes, rather than 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.