Skip to main content
GitLab CI
CHAPTER 06

Docker and GitLab CI

Updated: May 15, 2026
20 min read

# CHAPTER 6

Docker and GitLab CI

1. Introduction

The phrase "It works on my machine!" is the most dangerous sentence in software engineering. Code that runs perfectly on a developer's Mac laptop will inexplicably crash when deployed to an Ubuntu production server because of hidden differences in operating system versions or installed software. To solve this, the industry universally adopted Docker. Docker packages your code, its dependencies, and a mini-operating system into a single, standardized box (a Container). In this chapter, we will learn how GitLab CI integrates seamlessly with Docker, ensuring that the environment where your code is tested is mathematically identical to the environment where your code runs.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Define the core concepts of Docker (Images vs. Containers).
  • Understand the image: keyword in .gitlab-ci.yml.
  • Use the GitLab Container Registry.
  • Configure a CI pipeline to build and push a Docker Image.
  • Understand the concept of "Docker-in-Docker" (dind).

3. Beginner Explanation

Imagine you are shipping a highly sensitive, exotic plant to another country.
  • The Old Way: You dig up the plant, throw it in the mail, and hope the soil and climate in the new country will keep it alive. It usually dies.
  • Docker: You build a high-tech glass terrarium. You put the plant inside, along with its specific soil, a UV light, and a sprinkler system. You seal the terrarium. Now, you can ship this sealed box anywhere in the world—to the Arctic or the Sahara—and the plant will survive because it carries its exact environment with it.

A Docker Image is the instruction manual for building the terrarium. A Docker Container is the physical terrarium itself.

4. The image Keyword

In the previous chapter, we used image: php:8.2-cli in our testing pipeline. What did that actually do?
  1. 1. The GitLab Runner saw the job.
  1. 2. It contacted Docker Hub (a public library of images).
  1. 3. It downloaded a pristine, pre-configured Linux server image that had PHP 8.2 installed.
  1. 4. It started a new Container (the terrarium) from that image.
  1. 5. It ran your phpunit script inside that isolated container.
  1. 6. When the script finished, it immediately deleted the container, leaving no messy files behind.

5. Building Your Own Docker Image

Often, you don't just want to use a public Docker image to test your code; you want your pipeline to *create* a Docker image of your actual application, so you can deploy that image to a cloud server.

To do this, you must write a Dockerfile in your repository. Then, you configure GitLab CI to build it.

yaml
1234567891011121314
stages:
  - build

build_docker_image:
  stage: build
  # To build a Docker image inside a GitLab CI pipeline, 
  # the Runner itself needs Docker installed. This is called Docker-in-Docker.
  image: docker:20.10.16
  services:
    - docker:20.10.16-dind
  
  script:
    # Build the image using the Dockerfile
    - docker build -t my-app-image:latest .

6. Mini Project: Build and Push Docker Image

Building the image is useless unless we save it somewhere. GitLab provides a built-in Container Registry (a private shelf to store your custom terrariums).

Let's configure a pipeline that builds an image and securely pushes it to your project's private registry.

yaml
12345678910111213141516
build_and_push:
  stage: build
  image: docker:20.10.16
  services:
    - docker:20.10.16-dind
  
  # These predefined variables ($CI_REGISTRY) are automatically generated by GitLab!
  script:
    # 1. Log in to the GitLab Registry securely
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    
    # 2. Build the image and tag it with the project path
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    
    # 3. Push the finalized image to the GitLab shelf
    - docker push $CI_REGISTRY_IMAGE:latest

7. The Power of Predefined Variables

In the script above, you didn't have to hardcode your username or password. GitLab provides over 100 Predefined Variables (like $CIREGISTRYUSER and $CICOMMITSHA). GitLab automatically injects these into your pipeline when it runs. This makes your YAML files incredibly secure and reusable across different projects.

8. Best Practices

  • Use Specific Tags: When specifying an image in your pipeline, never use image: php:latest. The latest tag points to whatever the newest version is today. In 6 months, PHP 9.0 might be released, and your pipeline will automatically use it and crash because your code isn't compatible. Always pin exact versions: image: php:8.2.4.

9. Common Mistakes

  • Forgetting services: docker:dind: If you try to run docker build inside a GitLab CI job without specifying the Docker-in-Docker service, the pipeline will instantly crash with the error: "Cannot connect to the Docker daemon." The runner needs the service to execute nested container commands.

10. Exercises

  1. 1. What is the fundamental problem that Docker solves in the software deployment lifecycle?
  1. 2. Explain the purpose of the GitLab Container Registry in relation to the CI/CD pipeline.

11. FAQs

Q: Can I use images from AWS Elastic Container Registry (ECR) instead of Docker Hub or GitLab? A: Yes! You can use the image: keyword to point to any registry on the internet (e.g., image: 12345.dkr.ecr.aws.com/my-image:latest). You just need to ensure the GitLab Runner has the proper AWS credentials to authenticate and download it.

12. Summary

In Chapter 6, we bridged the gap between code compilation and environment consistency by introducing Docker. We learned that the image keyword is the mechanism by which GitLab summons perfectly isolated, tailored environments for each CI job. We transitioned from simply executing scripts to building and packaging our entire application into a portable Docker Container. By leveraging Docker-in-Docker (dind) and GitLab's built-in Container Registry, we mastered the modern deployment artifact, ensuring our code will run flawlessly regardless of the server it lands on.

13. Next Chapter Recommendation

Our code is tested, containerized, and sitting on the shelf in the registry. How do we get it onto the live internet? Proceed to Chapter 7: Deployment Automation.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·