Docker in DevOps Pipelines
Integrate Docker into your DevOps pipelines using Jenkins, GitHub Actions, and GitLab CI. Learn build, test, scan, and deployment patterns for modern CI/CD workflows.
Docker transforms CI/CD pipelines by providing consistent, reproducible environments across build, test, and deployment stages. Instead of maintaining separate build agents with different tool versions, you can use Docker containers to create isolated, ephemeral environments that match production.
Key benefits: consistent build environments (no more "works on my machine"), faster pipeline execution with caching, easy parallel testing across multiple versions, and seamless integration with container registries for deployment.
Jenkins has excellent Docker integration through the Docker Pipeline plugin. You can run entire builds inside containers, build images, push to registries, and deploy from within Jenkins pipelines.
// Jenkinsfile - Docker Pipeline
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'myregistry.azurecr.io'
IMAGE_NAME = 'myapp'
IMAGE_TAG = "${env.BUILD_NUMBER}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Test in Container') {
agent {
docker {
image 'node:18-alpine'
args '-v /var/run/docker.sock:/var/run/docker.sock'
}
}
steps {
sh 'npm install'
sh 'npm test'
sh 'npm run lint'
}
}
stage('Build Docker Image') {
steps {
script {
docker.build("${IMAGE_NAME}:${IMAGE_TAG}")
}
}
}
stage('Scan Image') {
steps {
sh "trivy image ${IMAGE_NAME}:${IMAGE_TAG} --severity CRITICAL --exit-code 1"
}
}
stage('Push to Registry') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'azure-registry-creds') {
docker.image("${IMAGE_NAME}:${IMAGE_TAG}").push()
docker.image("${IMAGE_NAME}:${IMAGE_TAG}").push('latest')
}
}
}
}
stage('Deploy') {
steps {
sh '''
ssh deploy-server "cd /app && \
docker pull ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} && \
docker-compose up -d --no-deps app"
'''
}
}
}
post {
failure {
slackSend(color: 'danger', message: "Build failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}")
}
success {
slackSend(color: 'good', message: "Build succeeded: ${env.JOB_NAME} - ${env.BUILD_NUMBER}")
}
}
}
# Docker Compose for Jenkins (docker-compose.jenkins.yml)
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
jenkins_home:
GitHub Actions provides native Docker support. You can use pre-built actions, run jobs in containers, build and push images to GHCR, and deploy to cloud platforms.
# .github/workflows/docker-ci-cd.yml
name: Docker CI/CD Pipeline
on:
push:
branches: [ main ]
tags: ['v*']
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:18-alpine
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: npm run lint
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,format=short
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
needs: build-and-push
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
ssh staging-server "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} && docker-compose up -d"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Deploy to production
run: |
ssh prod-server "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} && docker-compose up -d"
GitLab CI has built-in Docker integration with the Docker executor. You can use Docker-in-Docker (DinD) to build images within CI jobs.
# .gitlab-ci.yml
image: docker:latest
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
stages:
- test
- build
- scan
- deploy
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
test:
stage: test
image: node:18-alpine
script:
- npm ci
- npm test
- npm run lint
build:
stage: build
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
- docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest
scan:
stage: scan
script:
- apk add --no-cache curl
- curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- trivy image --severity CRITICAL --exit-code 1 $IMAGE_TAG
deploy-staging:
stage: deploy
environment: staging
script:
- ssh staging-server "docker pull $IMAGE_TAG && docker-compose up -d"
only:
- main
deploy-production:
stage: deploy
environment: production
script:
- ssh prod-server "docker pull $IMAGE_TAG && docker-compose up -d"
only:
- tags
There are two main approaches to running Docker commands inside CI pipelines: Docker-in-Docker (DinD) and mounting the host's Docker socket.
# Docker-in-Docker (GitLab CI)
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
# Socket Mounting (Jenkins/GitHub Actions)
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# Comparison:
# DinD: More isolated, requires privileged mode, slower (nested virtualization)
# Socket Mounting: Faster, less isolation, shares host Docker daemon
# GitHub Actions - Docker layer caching
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push with cache
uses: docker/build-push-action@v5
with:
context: .
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
# GitLab CI - Cache Docker layers
variables:
DOCKER_BUILDKIT: 1
BUILDKIT_INLINE_CACHE: 1
build:
script:
- docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $IMAGE_TAG .
# Jenkins - Use build cache
docker.build("${IMAGE_NAME}:${IMAGE_TAG}", "--cache-from ${IMAGE_NAME}:latest .")
Integrate vulnerability scanning into your pipelines to catch security issues before deployment.
# Trivy in GitHub Actions
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
# Snyk in GitHub Actions
- name: Run Snyk
uses: snyk/actions/docker@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: ${{ env.IMAGE_NAME }}
args: --severity-threshold=high
# Docker Scout in GitHub Actions
- name: Run Docker Scout
uses: docker/scout-action@v1
with:
command: cves
image: ${{ env.IMAGE_NAME }}:latest
only-severities: critical,high
Build images for multiple platforms (linux/amd64, linux/arm64, linux/arm/v7) in CI pipelines.
# GitHub Actions - Multi-arch build
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push multi-arch
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}
# GitLab CI - Multi-arch
build:
script:
- docker buildx create --use
- docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_TAG --push .
- Use specific image tags - Never use `:latest` in CI pipelines. Pin to specific versions.
- Cache dependencies - Cache npm, pip, apt layers to speed up builds.
- Scan images before pushing - Catch vulnerabilities early in the pipeline.
- Use separate stages - Separate build, test, scan, and deploy into distinct stages.
- Tag images meaningfully - Use git commit SHA, branch name, and version tags.
- Implement rollbacks - Have a strategy to revert to previous versions.
- Use environment-specific configs - Different configs for dev, staging, prod.
- Monitor pipeline metrics - Track build times, success rates, and deployment frequency.
Integrating Docker into your CI/CD pipelines enables consistent, reproducible, and secure application delivery. Choose the right tools and patterns for your team's workflow.