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 gives you an easy-to-use GitLab Runner cloud solution designed to handle your GitLab CI/CD jobs efficiently. Here is what you can look forward to:
- Unlimited CI/CD minutes: there is no per-minute billing, so your pipelines can run whenever you need them.
- Multiple concurrent jobs: run several jobs at the same time to speed up your entire pipeline.
- The Docker executor with Docker-in-Docker support: streamline 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: you can connect your first GitLab Runner and start running pipelines in just a few minutes!
Introduction
With a Stackhero GitLab Runner, every job runs inside a fresh 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, so you are able 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 build as often as needed, without worrying about usage limits. 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 helps your pipelines complete faster.
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 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 the name you want:
- 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 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 extra 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 a different 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 leverage Docker's layer caching, so only new or changed layers are rebuilt.
Running jobs in parallel
Your plan determines how many jobs can run concurrently. Jobs in the same stage start together, up to your concurrency limit. This means several independent jobs can run in parallel, finishing as soon as the slowest job completes, rather than waiting for each to finish in sequence.
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 details on building Docker images in GitLab CI/CD pipelines, you can consult the official GitLab documentation on using Docker builds.