← Back to Docker Mastery
Advanced14 min read

Docker in CI/CD

Integrate Docker into CI/CD pipelines — build and push images in GitHub Actions, GitLab CI, and Jenkins with caching, scanning, and deployment automation.

CI Pipeline Fundamentals

A Docker CI pipeline builds the image, runs tests inside it, scans for vulnerabilities, pushes to a registry, and triggers deployment. Each commit produces a tagged, immutable artifact.

Build once, deploy everywhere — the same image tested in CI is deployed to staging and production. Environment-specific configuration is injected at runtime, not build time.

  • Tag images with git SHA for traceability
  • Never rebuild images between environments
  • Fail the pipeline on critical CVE scan results
# Typical pipeline stages
# 1. Build image
# 2. Run unit tests (inside container)
# 3. Run integration tests
# 4. Scan for CVEs
# 5. Push to registry
# 6. Deploy to environment

GitHub Actions

Use docker/build-push-action for building and pushing. Log in to registries with docker/login-action. Cache layers with cache-from and cache-to for faster builds.

GitHub Actions runners have Docker pre-installed. Use buildx for multi-platform builds and BuildKit cache export for persistent layer caching across workflow runs.

- uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/build-push-action@v5
  with:
    push: true
    tags: ghcr.io/org/myapp:${{ github.sha }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

Testing in Containers

Run tests inside the built image to validate the exact artifact that will deploy. Use docker run or compose to start dependencies (databases, caches) for integration tests.

Testcontainers libraries spin up real database containers during test runs — no mocks needed. This validates that your application works with actual PostgreSQL, Redis, or Kafka.

# Run tests in CI
docker build -t myapp:test .
docker run --rm myapp:test npm test

# With test dependencies
docker compose -f compose.test.yml up -d db redis
docker run --rm --network compose_default myapp:test npm run test:integration
docker compose -f compose.test.yml down

Registry and Deployment

Push images to a registry accessible by your deployment target. Use immutable tags (SHA-based) in production. Automate deployment with kubectl set image, docker service update, or ECS task definition updates.

Implement deployment gates: require manual approval for production, run smoke tests after deploy, and auto-rollback on health check failure.

# Deploy to Kubernetes after push
- run: |
    kubectl set image deployment/myapp \
      myapp=ghcr.io/org/myapp:${{ github.sha }}
    kubectl rollout status deployment/myapp --timeout=300s

Pipeline Optimization

Cache Docker layers between CI runs using registry cache or GitHub Actions cache. Use multi-stage builds so only the final stage is pushed. Parallelize test and scan stages.

Use .dockerignore aggressively to minimize build context size. A large build context slows every build regardless of layer caching.

# BuildKit cache in GitHub Actions
- uses: docker/build-push-action@v5
  with:
    cache-from: type=registry,ref=ghcr.io/org/myapp:buildcache
    cache-to: type=registry,ref=ghcr.io/org/myapp:buildcache,mode=max

Get In Touch


Ready to discuss your next project? Drop me a message.