GitLab CI/CD – Image

In today’s blog post, we will discuss image keyword in .gitlab-ci.yml file. Specifically, what is image keyword in .gitlab-ci.yml?, how to define image with example?, tips and best practices of using image in GitLab CI/CD pipeline. So without any further delay, let us get started.

What is the image Keyword?

The image keyword in your .gitlab-ci.yml file allows you to define the Docker image that GitLab Runner will use to execute the before_script, script, and after_script commands for a job.

When a job starts, the GitLab Runner (specifically, if it is configured with a Docker or Kubernetes executor) will:

  1. Pull the specified Docker image.
  2. Start a new container based on that image.
  3. Mount your project’s code into this container.
  4. Execute the job’s commands inside this isolated container.

This ensures a clean, consistent, and reproducible environment for every job run.

Where to Define the image Keyword

You can define the image keyword at two primary levels:

Global (Default) Level

Purpose: To set a default Docker image that applies to all jobs in your .gitlab-ci.yml file, unless overridden by a job-specific image.

Location: At the top level of your .gitlab-ci.yml file.

Example:

default:
  image: node:20-alpine # All jobs will use Node.js 20 on Alpine Linux by default

stages:
  - build
  - test

build_frontend:
  stage: build
  script:
    - npm install
    - npm run build

run_unit_tests:
  stage: test
  script:
    - npm test

Explanation: Both build_frontend and run_unit_tests jobs will automatically use the node:20-alpine Docker image.

Job-Specific Level

Purpose: To override the global default:image and specify a different Docker image for a particular job. This is useful when different jobs require different toolsets or environments.

Location: Within an individual job definition.

Example:

default:
  image: node:20-alpine # Default for most jobs

stages:
  - build
  - test
  - deploy

build_frontend:
  stage: build
  script: npm run build

run_backend_tests:
  stage: test
  image: python:3.10-slim-buster # This job uses a Python image
  script:
    - pip install -r requirements.txt
    - pytest

deploy_to_cloud:
  stage: deploy
  image: google/cloud-sdk:latest # This job uses Google Cloud SDK image
  script:
    - gcloud app deploy

Explanation:

  • build_frontend uses node:20-alpine (from default).
  • run_backend_tests uses python:3.10-slim-buster.
  • deploy_to_cloud uses google/cloud-sdk:latest.

How to Specify Docker Images

You can specify images from various sources:

  1. Docker Hub (Official and Community Images): Most commonly used.
    • image: ubuntu:latest
    • image: maven:3.8.6-openjdk-17
    • image: golang:1.20
  2. GitLab Container Registry: If your project hosts its own Docker images in the GitLab Container Registry (e.g., for custom build environments or application images).
    • image: registry.gitlab.com/your-group/your-project/my-custom-build-image:1.0
    • image: $CI_REGISTRY_IMAGE/my-app:latest (using predefined variables for project path)
  3. Other Private Registries: If you have credentials configured for external private registries (e.g., Amazon ECR, Google Container Registry).
    • image: my.private.registry.com/my-image:tag

Best Practices for Using the image Keyword

  • Use Specific Tags, Not latest for Production/Stable Builds: While latest might be convenient for development, it can lead to non-reproducible builds as the latest tag constantly changes. For stable builds, releases, or critical paths, always pin to a specific version tag (e.g., node:20.10.0-alpine, python:3.9.18-slim-buster).
# Good
image: node:20.10.0-alpine

# Bad (for stable builds)
# image: node:latest
  • Choose Minimal Images: Opt for slim or alpine variants of images where possible. They are smaller, pull faster, and reduce the attack surface. Install only the necessary tools within your before_script or script.
# Good
image: python:3.10-slim-buster

# Potentially too large if you only need Python
# image: python:3.10
  • Build Custom Images for Complex Environments: If your pipeline jobs require a very specific set of tools and dependencies that are not readily available in public images (or are too large to install repeatedly in before_script), create your own custom Docker image.
    • This image would contain all your tools pre-installed.
    • Build this image with a separate CI/CD pipeline and push it to your GitLab Container Registry.
    • Then, reference your custom image in your .gitlab-ci.yml. This speeds up subsequent job runs significantly.
  • Balance Global vs. Job-Specific:
    • Use a global default:image for the most common environment needed by the majority of your jobs.
    • Override with job-specific image only when a different environment is strictly necessary. This keeps your .gitlab-ci.yml clean.
  • Consider Runners’ Executors: The image keyword is primarily relevant for GitLab Runners using the Docker or Kubernetes executors. If your runner is using the Shell executor, the image keyword is ignored, and jobs run directly on the host machine (which is generally less reproducible and not recommended for most scenarios).
  • Security and Trust: Be mindful of the source of your Docker images. Stick to official images from trusted sources or images you build yourself.

FAQs – Image


What is the image keyword in GitLab CI/CD?
The image keyword in GitLab CI/CD specifies the Docker image to be used by the GitLab Runner when executing a job. This allows jobs to run in a pre-configured environment with the necessary tools, such as Node.js, Python, Java, or any custom image.


How do I use the image keyword in a job?
You can define an image per job by using the image keyword:

build:
  image: node:20
  script:
    - node --version
    - npm install

In this example, the job runs inside a container using the official node:20 image from Docker Hub.


Can I define a global image for all jobs?
Yes. You can specify a global image at the top level of the .gitlab-ci.yml file, and it will apply to all jobs, unless overridden:

image: python:3.11

lint:
  script:
    - python --version
    - flake8 .

All jobs will use the python:3.11 image unless they specify their own.


How do I use a private image registry with the image keyword?
You can reference private Docker registries in the image keyword. If credentials are needed, configure them under CI/CD settings → Variables:

image: registry.example.com/my-group/custom-image:latest

Also define CI variables like CI_REGISTRY_USER and CI_REGISTRY_PASSWORD if needed.


Can I override the global image in specific jobs?
Yes. Any job can override the globally defined image:

image: ruby:3.2

rspec:
  image: ruby:3.1
  script:
    - ruby --version
    - bundle exec rspec

In this example, the global image is ruby:3.2, but the rspec job uses ruby:3.1.


Can I use custom Docker images in GitLab CI/CD?
Yes. You can use your own custom-built Docker images by hosting them on:

  • Docker Hub
  • GitLab Container Registry
  • AWS ECR, GCR, or other registries

Example:

image: registry.gitlab.com/myproject/custom-ci-image:latest

Make sure the image contains all the tools required by your pipeline.


Can I specify a tag or use latest in the image name?
Yes. You can specify a particular tag, or omit it to use latest by default:

image: golang:1.21

or

image: golang  # This uses golang:latest

Using explicit versions is recommended for pipeline reproducibility.


Is the image keyword required in .gitlab-ci.yml?
No. If you do not define an image, the GitLab Runner uses its default shell executor, which runs directly on the host. However, using image with the Docker executor ensures clean, isolated, and reproducible environments.


Can I pull images from GitLab’s own Container Registry?
Yes. If your image is stored in your GitLab project’s registry, use:

image: registry.gitlab.com/<namespace>/<project>/<image>:tag

You may need to authenticate using CI/CD variables if the image is private.


Can I use a Docker image with entrypoint defined?
Yes. GitLab will honor the ENTRYPOINT defined in the Docker image. If needed, you can override it using the entrypoint keyword:

job:
  image:
    name: alpine:latest
    entrypoint: [""]
  script:
    - echo "Running without entrypoint"

Setting entrypoint: [""] disables the image’s default entrypoint.


Does each job get a new container from the image?
Yes. Each job gets a fresh container from the specified image. This ensures jobs are isolated and do not share state, which improves consistency and security.


What happens if the image cannot be pulled?
If GitLab Runner fails to pull the image (e.g., due to authentication errors or the image not existing), the job fails immediately, and the pipeline stops at that stage.


Author

Debjeet Bhowmik

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com

Leave a Comment