TechLead
DevOps
February 8, 202610 min read

Setting Up CI/CD with Docker and GitHub Actions: A 2026 Guide

Learn how to build a complete CI/CD pipeline using Docker and GitHub Actions. From multi-stage builds to automated deployments, this guide covers everything you need for production-ready workflows.

By TechLead
Docker
CI/CD
GitHub Actions
DevOps
Deployment

A well-designed CI/CD pipeline is the backbone of modern software delivery. By combining Docker for consistent environments with GitHub Actions for automation, you can build, test, and deploy with confidence. This guide walks you through setting up a production-grade pipeline from scratch.

1. Why Docker + GitHub Actions?

  • Reproducibility: Docker ensures your app builds and runs the same way everywhere — your laptop, CI server, and production.
  • Speed: Multi-stage builds and layer caching dramatically reduce build times.
  • Free tier: GitHub Actions offers 2,000 minutes/month for free on public repos.

2. Writing a Production Dockerfile

Use multi-stage builds to keep your final image lean:

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: Production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["npm", "start"]

This pattern typically reduces image size by 60-80% compared to a single-stage build.

3. GitHub Actions Workflow

Create .github/workflows/deploy.yml:

name: Build and Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: false
          load: true
          tags: myapp:test

      - name: Run tests in container
        run: docker run --rm myapp:test npm test

  deploy:
    needs: build-and-test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to 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

4. Key Optimizations

  • Layer caching: Docker builds only changed layers. Order your Dockerfile from least-to-most frequently changed (OS → dependencies → source code).
  • BuildKit: docker/setup-buildx-action enables BuildKit by default, giving you parallel builds and better caching.
  • .dockerignore: Exclude node_modules, .git, and test files to speed up the build context.

5. Adding Health Checks

Add a health check to your Dockerfile so orchestrators know when your app is ready:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3   CMD curl -f http://localhost:3000/api/health || exit 1

6. Security Best Practices

  • Never store secrets in Docker images. Use GitHub Actions secrets and inject them at runtime.
  • Run containers as a non-root user: USER node
  • Scan images for vulnerabilities: docker scout cves myapp:latest
  • Pin base image versions instead of using :latest