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.

Jenkins GitHub Actions GitLab CI Security Scanning
Why Docker in DevOps Pipelines?

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.

Modern CI/CD pipelines are increasingly container-native. Use Docker-in-Docker (DinD) or Docker socket mounting to enable container builds within CI runners.
Jenkins Pipeline with Docker

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 with Docker

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 with Docker

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
Docker-in-Docker (DinD) vs Socket Mounting

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
For most CI/CD pipelines, socket mounting is simpler and faster. Use DinD when you need complete isolation or are running untrusted code.
Caching Strategies for Faster Pipelines
# 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 .")
Security Scanning in CI/CD Pipelines

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
Multi-Architecture Docker Builds

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 .
DevOps Pipeline Best Practices
  • 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.
Frequently Asked Questions
What's the difference between Docker-in-Docker and socket mounting?
DinD runs a separate Docker daemon inside a container (requires privileged mode). Socket mounting shares the host's Docker socket, making builds faster but less isolated. For most CI/CD, socket mounting is simpler and faster.
How do I handle Docker credentials in CI pipelines?
Use CI/CD secrets management: GitHub Secrets, GitLab CI variables, Jenkins credentials. Never hardcode credentials. Use `docker login` with secrets, or use OIDC for cloud registry authentication.
Which CI/CD tool has the best Docker integration?
All three have excellent Docker support. GitHub Actions has the most modern integration with built-in actions. GitLab CI has native Docker executor. Jenkins has the most flexibility but requires more setup.
How do I cache Docker layers in CI?
Use BuildKit with cache-from and cache-to. GitHub Actions has built-in cache (type=gha). GitLab CI can use inline cache or registry cache. Jenkins can use --cache-from with previous image.
Should I build images in CI or use pre-built base images?
Build in CI for application-specific images. Use pre-built base images for common dependencies to speed up builds. Cache base image layers to avoid rebuilding them.
How do I test Docker images in CI pipelines?
Run containers in CI: `docker run --rm myapp npm test`. Use `docker-compose` for multi-service tests. Run integration tests against ephemeral databases in containers.
What's the best way to deploy from CI?
Push to registry, then use SSH to pull and restart, or use orchestration tools (Kubernetes, Swarm, ECS). For cloud, use cloud provider CLIs (aws, az, gcloud) with OIDC authentication.
How do I handle secrets during Docker build in CI?
Use BuildKit secrets: `--mount=type=secret`. Never use `--build-arg` for secrets as they persist in image history. Use secret mounts that are only available during build.
Previous: Real-World Docker Projects Next: Docker Cheatsheet

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.