You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Docker and CI/CD are natural partners. Containers provide consistent, reproducible environments for building, testing, and deploying applications. This lesson covers how to build Docker images in CI, optimise layer caching, run tests in containers, and push images to registries — with practical examples for GitHub Actions and GitLab CI.
| Benefit | Description |
|---|---|
| Reproducibility | Same image in CI, staging, and production |
| Isolation | Build and test in a clean environment every time |
| Speed | Layer caching reduces build times |
| Portability | The same pipeline works regardless of the CI platform |
| Artefact management | The image IS the deployable artefact |
# .github/workflows/build.yml
name: Build and Push
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: \${{ secrets.DOCKER_USERNAME }}
password: \${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: \${{ github.event_name != 'pull_request' }}
tags: |
myuser/my-app:latest
myuser/my-app:\${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# .gitlab-ci.yml
stages:
- build
- test
- push
variables:
IMAGE_TAG: \$CI_REGISTRY_IMAGE:\$CI_COMMIT_SHA
IMAGE_LATEST: \$CI_REGISTRY_IMAGE:latest
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u \$CI_REGISTRY_USER -p \$CI_REGISTRY_PASSWORD \$CI_REGISTRY
- docker build
--cache-from \$IMAGE_LATEST
--tag \$IMAGE_TAG
--tag \$IMAGE_LATEST
.
- docker push \$IMAGE_TAG
- docker push \$IMAGE_LATEST
Without caching, every CI build rebuilds all layers from scratch. This is slow and wasteful.
| Strategy | How It Works | Speed |
|---|---|---|
| Registry cache | Pull previous image and use --cache-from | Good |
| GitHub Actions cache | BuildKit GHA cache (type=gha) | Better |
| BuildKit inline | Cache metadata embedded in the image | Good |
| Local cache | Persistent cache directory on the CI runner | Best |
# Pull the previous image for caching (ignore failure if first build)
docker pull myuser/my-app:latest || true
# Build using the previous image as cache
docker build \
--cache-from myuser/my-app:latest \
--tag myuser/my-app:\$COMMIT_SHA \
--tag myuser/my-app:latest \
.
For package managers (npm, pip, go mod), use BuildKit cache mounts to persist the package cache across builds:
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
CMD ["node", "server.js"]
# Multi-stage: test stage
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Test stage — fails the build if tests fail
FROM base AS test
RUN npm test
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.