You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Containers have revolutionised CI/CD by providing consistent, isolated, and reproducible build environments. This lesson covers Docker in CI/CD, multi-stage builds, image registries, and best practices for containerised pipelines.
Without containers:
Developer's machine: Node 20, npm 10, Python 3.12, Go 1.22
CI server: Node 18, npm 9, Python 3.9, Go 1.20
Production: Node 20, npm 10, Python 3.11, Go 1.22
→ "Works on my machine" problems
With containers:
Developer: docker run node:20-alpine npm test ✓
CI server: docker run node:20-alpine npm test ✓
Production: docker run node:20-alpine npm start ✓
→ Same environment everywhere
| Benefit | Description |
|---|---|
| Consistency | Same environment from dev to production |
| Isolation | Builds do not interfere with each other |
| Reproducibility | Pin exact image versions for deterministic builds |
| Speed | Pre-built images with dependencies already installed |
| Portability | Run on any CI platform that supports Docker |
| Concept | Description |
|---|---|
| Image | A read-only template containing code, runtime, and dependencies |
| Container | A running instance of an image |
| Dockerfile | Instructions for building an image |
| Registry | A storage service for images (Docker Hub, GHCR, ECR) |
| Layer | Each Dockerfile instruction creates a cached layer |
| Tag | A version label for an image (e.g., myapp:v1.2.3) |
# Build an image
docker build -t myapp:v1.0.0 .
# Run a container
docker run --rm myapp:v1.0.0
# Push to a registry
docker push ghcr.io/org/myapp:v1.0.0
# Pull from a registry
docker pull ghcr.io/org/myapp:v1.0.0
# List images
docker images
# Remove unused images
docker image prune -f
Multi-stage builds keep your production images small by separating the build environment from the runtime environment:
# ── Stage 1: Build ──
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ── Stage 2: Production ──
FROM node:20-alpine AS runner
WORKDIR /app
# Copy only what we need from the builder
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
USER node
CMD ["npm", "start"]
| Approach | Image Size |
|---|---|
| Single stage (with dev deps) | ~800 MB |
| Multi-stage (production only) | ~200 MB |
| Multi-stage + Alpine | ~150 MB |
| Distroless | ~80 MB |
name: Build and Push Docker Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.