Skip to main content

Docker Build

The docker-build jobs are used to build Docker container images using Docker-in-Docker (DinD) or Docker Compose. These jobs provide different build strategies for various use cases.

Overview

The docker-build jobs include:

  • .docker-build: Standard Docker build using Docker-in-Docker
  • .docker-build-vm-setup: Docker build with VM setup that extracts files from the container
  • .docker-compose-build: Docker Compose build for multi-container applications
  • .docker-build-multi: Build multiple images from a directory structure

Variables

The following variables can be configured:

VariableDescriptionDefaultRequired
DOCKER_BUILD_IMAGEDocker image to use for builds'docker:27'No
DOCKER_BUILD_SERVICEDocker-in-Docker service version'27-dind'No
DOCKER_COMPOSE_IMAGEDocker Compose image'docker:27'No
DOCKER_COMPOSE_SERVICEDocker Compose service version'27-dind'No
APP_ENVIRONMENTTarget environment (develop, staging, production)-Yes
STORAGE_USER_IDUser ID for storage permissions-Yes
VERSIONImage version tag (auto-generated for develop)-No

GitLab CI/CD Variables

The jobs use the following built-in GitLab CI/CD variables:

  • CI_REGISTRY_USER: GitLab registry username
  • CI_REGISTRY_PASSWORD: GitLab registry password
  • CI_REGISTRY: GitLab container registry URL
  • CI_REGISTRY_IMAGE: The address of the container registry tied to the project
  • CI_COMMIT_REF_NAME: Branch or tag name
  • CI_PIPELINE_ID: Unique ID of the current pipeline
  • CI_COMMIT_SHORT_SHA: First 8 characters of the commit revision
  • CI_PROJECT_NAME: Project name

Job Types

.docker-build

Standard Docker build job that builds and pushes a single Docker image.

Usage

variables:
DOCKER_BUILD_IMAGE: 'docker:27'
DOCKER_BUILD_SERVICE: '27-dind'
APP_ENVIRONMENT: 'develop'
STORAGE_USER_ID: '1000'

.docker-build:
image: $DOCKER_BUILD_IMAGE
services:
- docker:$DOCKER_BUILD_SERVICE
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- |-
if [[ $APP_ENVIRONMENT = 'develop' ]]; then
export VERSION=$CI_COMMIT_REF_NAME-$CI_PIPELINE_ID
echo "Application image = $CI_REGISTRY_IMAGE:${VERSION}"
fi

if [[ $APP_ENVIRONMENT = 'staging' ]]; then
echo "Application image = $CI_REGISTRY_IMAGE:${VERSION}"
fi

if [[ $APP_ENVIRONMENT = 'production' ]]; then
echo "Application image = $CI_REGISTRY_IMAGE:${VERSION}"
fi

export CONTAINER_IMAGE=$CI_REGISTRY_IMAGE:$VERSION
docker build --build-arg APP_UID=$STORAGE_USER_ID --build-arg ENVIRONMENT=$APP_ENVIRONMENT -t ${CONTAINER_IMAGE} .
echo "pushing image ${CONTAINER_IMAGE}"
docker push ${CONTAINER_IMAGE}
echo "pushed image ${CONTAINER_IMAGE}"
echo "VERSION=$VERSION" > .env.version
cat .env.version

Version Tagging

  • develop: {CI_COMMIT_REF_NAME}-{CI_PIPELINE_ID} (e.g., main-12345)
  • staging: Uses $VERSION variable (must be set)
  • production: Uses $VERSION variable (must be set)

Build Arguments

  • APP_UID: Set to $STORAGE_USER_ID for file permissions
  • ENVIRONMENT: Set to $APP_ENVIRONMENT

Output

Creates a .env.version file containing the VERSION variable for use in subsequent jobs.

.docker-build-vm-setup

Docker build job that extracts files from the built container for VM deployment.

Usage

.docker-build-vm-setup:
image: $DOCKER_BUILD_IMAGE
services:
- docker:$DOCKER_BUILD_SERVICE
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- |-
if [[ $APP_ENVIRONMENT = 'develop' ]]; then
export VERSION=$CI_COMMIT_REF_NAME-$CI_PIPELINE_ID
echo "[DEVELOP] Application image = $CI_REGISTRY_IMAGE:${VERSION}"
fi

if [[ $APP_ENVIRONMENT = 'staging' ]]; then
source .env.version
echo "[STAGING] Application image = $CI_REGISTRY_IMAGE:${VERSION}"
fi

if [[ $APP_ENVIRONMENT = 'production' ]]; then
source .env.version
echo "[PRODUCTION] Application image = $CI_REGISTRY_IMAGE:${VERSION}"
fi

docker run --name deploy -d $CI_REGISTRY_IMAGE:$VERSION sleep infinity
mkdir -p dockerimage && docker cp deploy:/app ./dockerimage/

Differences from .docker-build

  • For staging/production, it sources .env.version from a previous job
  • Runs the container and extracts files from /app to ./dockerimage/
  • Used for VM-based deployments where you need the application files

.docker-compose-build

Docker Compose build job for multi-container applications.

Usage

variables:
DOCKER_COMPOSE_IMAGE: 'docker:27'
DOCKER_COMPOSE_SERVICE: '27-dind'

.docker-compose-build:
image: $DOCKER_COMPOSE_IMAGE
services:
- name: docker:$DOCKER_COMPOSE_SERVICE
command: ["--tls=false"]
variables:
DOCKER_TLS_CERTDIR: ""
DOCKER_HOST: tcp://docker:2375
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script: |
if [[ $APP_ENVIRONMENT = 'develop' ]]; then
export VERSION=$CI_COMMIT_REF_NAME-$CI_PIPELINE_ID
echo "Application image = $CI_REGISTRY_IMAGE:${VERSION}"
cp .env.dev .env
fi

if [[ $APP_ENVIRONMENT = 'staging' ]]; then
echo "Application image = $CI_REGISTRY_IMAGE:${VERSION}"
cp .env.staging .env
fi

if [[ $APP_ENVIRONMENT = 'production' ]]; then
echo "Application image = $CI_REGISTRY_IMAGE:${VERSION}"
cp .env.production .env
fi

export CONTAINER_IMAGE=$CI_REGISTRY_IMAGE:$VERSION

# use the plugin:
docker compose build --build-arg APP_UID="$STORAGE_USER_ID" app

docker tag ${CI_PROJECT_NAME}-app:latest ${CONTAINER_IMAGE}
docker push "${CONTAINER_IMAGE}"
rm .env
echo "pushed image ${CONTAINER_IMAGE}"
echo "VERSION=$VERSION" > .env.version
cat .env.version

Features

  • Uses Docker Compose to build services defined in docker-compose.yml
  • Automatically copies environment-specific .env files (.env.dev, .env.staging, .env.production)
  • Builds the app service with build arguments
  • Tags and pushes the built image
  • Disables TLS for Docker-in-Docker communication

Environment Files

The job automatically selects the appropriate environment file:

  • develop: Copies .env.dev to .env
  • staging: Copies .env.staging to .env
  • production: Copies .env.production to .env

.docker-build-multi

Builds multiple Docker images from a directory structure.

Usage

.docker-build-multi:
image: $DOCKER_BUILD_IMAGE
services:
- docker:$DOCKER_BUILD_SERVICE
variables:
IMAGES_ROOT: "images"
IMAGES: ""
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script: |
# ... (see full script in the job definition)

Variables

  • IMAGES_ROOT: Root folder where per-image directories live (default: "images")
  • IMAGES: Space-separated list of image folders to build. If empty, builds ALL subdirectories in IMAGES_ROOT

Behavior

  1. Auto-detection: If IMAGES is empty, automatically detects all subdirectories in IMAGES_ROOT
  2. Selective build: If IMAGES is specified, only builds those directories
  3. Version tagging: Uses the same versioning logic as .docker-build
  4. Image naming: Images are tagged as {CI_REGISTRY_IMAGE}/{image-name}:{VERSION}

Example Directory Structure

images/
├── api/
│ └── Dockerfile
├── worker/
│ └── Dockerfile
└── frontend/
└── Dockerfile

This would build three images:

  • {CI_REGISTRY_IMAGE}/api:{VERSION}
  • {CI_REGISTRY_IMAGE}/worker:{VERSION}
  • {CI_REGISTRY_IMAGE}/frontend:{VERSION}

Usage Examples

Build all images:

build-all:
extends: .docker-build-multi
variables:
IMAGES_ROOT: "images"
IMAGES: "" # Empty = build all

Build specific images:

build-selected:
extends: .docker-build-multi
variables:
IMAGES_ROOT: "images"
IMAGES: "api frontend" # Only build api and frontend

Complete Pipeline Example

variables:
DOCKER_BUILD_IMAGE: 'docker:27'
DOCKER_BUILD_SERVICE: '27-dind'
APP_ENVIRONMENT: 'develop'
STORAGE_USER_ID: '1000'

stages:
- build
- deploy

.docker-build:
image: $DOCKER_BUILD_IMAGE
services:
- docker:$DOCKER_BUILD_SERVICE
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- |-
if [[ $APP_ENVIRONMENT = 'develop' ]]; then
export VERSION=$CI_COMMIT_REF_NAME-$CI_PIPELINE_ID
echo "Application image = $CI_REGISTRY_IMAGE:${VERSION}"
fi

export CONTAINER_IMAGE=$CI_REGISTRY_IMAGE:$VERSION
docker build --build-arg APP_UID=$STORAGE_USER_ID --build-arg ENVIRONMENT=$APP_ENVIRONMENT -t ${CONTAINER_IMAGE} .
docker push ${CONTAINER_IMAGE}
echo "VERSION=$VERSION" > .env.version

build:
extends: .docker-build
stage: build
artifacts:
reports:
dotenv: .env.version
only:
- branches

Prerequisites

  • Docker-in-Docker (DinD) service must be available
  • GitLab Container Registry must be enabled
  • A Dockerfile (or docker-compose.yml for compose builds) must exist
  • For .docker-build-vm-setup: Previous job must create .env.version
  • For .docker-compose-build: Environment-specific .env.* files must exist
  • For .docker-build-multi: Directory structure with Dockerfile in each subdirectory

Notes

  • Docker-in-Docker requires privileged mode on the runner
  • Version tagging differs by environment (develop uses branch-pipeline, others use VERSION variable)
  • The .env.version file can be used as a dotenv artifact to share version information
  • For multi-image builds, each subdirectory must contain its own Dockerfile
  • Docker Compose builds require a docker-compose.yml file with an app service defined