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=maxTesting 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=300sPipeline 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