Implementing Modern CI/CD Pipelines with GitHub Actions and Docker
Discover how to build robust, scalable CI/CD pipelines using GitHub Actions and Docker. From core concepts to advanced caching and security best practices, this guide provides actionable steps for modern software delivery.
Implementing Modern CI/CD Pipelines with GitHub Actions and Docker
Is your company ready for AI? Download our free checklist →
Download checklistIntroduction
In the fast-paced world of software development, delivering updates quickly and reliably is paramount. Continuous Integration and Continuous Deployment (CI/CD) have become the backbone of modern DevOps practices, enabling teams to automate the build, test, and deployment processes. Among the myriad of tools available, GitHub Actions and Docker stand out for their flexibility, integration, and community support.
According to the 2023 State of DevOps Report, high-performing organizations deploy code 208 times more frequently than low performers. This staggering difference is largely attributed to mature CI/CD pipelines. In this article, we'll explore how to implement modern CI/CD pipelines using GitHub Actions and Docker, covering everything from basic setup to advanced optimization techniques.
Understanding CI/CD and Its Importance
CI/CD is a method of frequently delivering apps to customers by introducing automation into the stages of app development. The main concepts attributed to CI/CD are continuous integration, continuous delivery, and continuous deployment.
- Continuous Integration (CI): Developers merge their changes back to the main branch frequently. Each merge triggers an automated build and test process, catching integration issues early.
- Continuous Delivery (CD): Code is automatically built, tested, and prepared for release to production, but the actual deployment is a manual step.
- Continuous Deployment: Every change that passes all stages of the production pipeline is released to customers automatically, with no human intervention.
Implementing CI/CD reduces manual errors, accelerates feedback loops, and ensures that software is always in a releasable state. GitHub Actions provides the orchestration, while Docker ensures consistency across environments.
Why GitHub Actions and Docker?
GitHub Actions is a powerful CI/CD platform integrated directly with GitHub repositories. It allows you to automate workflows based on events like pushes, pull requests, or issue creation. Docker, on the other hand, containerizes applications, ensuring that they run the same regardless of where they are deployed.
Together, they offer:
- Seamless Integration: GitHub Actions natively triggers on GitHub events, and Docker images can be built and pushed directly to container registries like GitHub Container Registry (GHCR) or Docker Hub.
- Scalability: GitHub Actions provides hosted runners with generous compute resources, and Docker containers can be scaled horizontally.
- Portability: Docker ensures that the environment in CI is identical to production, eliminating the classic "works on my machine" problem.
- Cost-Effectiveness: GitHub Actions offers 2,000 free minutes per month for private repositories, and self-hosted runners can further reduce costs.
Setting Up a Basic CI/CD Pipeline
Let's walk through creating a simple CI/CD pipeline for a Node.js application using GitHub Actions and Docker.
Prerequisites
- A GitHub repository with your application code.
- A Dockerfile in the repository root.
- (Optional) A container registry account (like Docker Hub or GitHub Container Registry).
Step 1: Create a Workflow File
In your repository, create a directory .github/workflows and add a YAML file, e.g., ci.yml.
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
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
tags: myapp:latest
- name: Run tests in Docker
run: |
docker run --rm myapp:latest npm test
This workflow triggers on pushes and pull requests to the main branch. It checks out the code, sets up Docker Buildx (for advanced build features), builds the Docker image, and runs tests inside the container.
Step 2: Add Deployment Job
To deploy automatically, add a deployment job that depends on the build job. Here's an example using a simple SSH deployment:
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull myregistry/myapp:latest
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 8080:8080 myregistry/myapp:latest
This job runs only on pushes to main, uses SSH to connect to your server, pulls the latest image, and runs it.
Best Practices for Optimizing Pipelines
To make your pipelines faster and more reliable, consider the following best practices:
Use Caching
Caching dependencies can drastically reduce build times. For Node.js, you can cache node_modules:
- name: Cache node_modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
For Docker, layer caching can be enabled with BuildKit:
Want a personalized diagnostic? Complete our free checklist →
Download checklist- name: Build with cache
uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
Use Matrix Builds
Matrix builds allow you to test against multiple versions of languages or operating systems simultaneously:
strategy:
matrix:
node-version: [18.x, 20.x]
os: [ubuntu-latest, windows-latest]
This ensures your code works across environments.
Keep Images Small
Use multi-stage builds to keep your Docker images lean. For example:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
This reduces the final image size, speeding up pushes and pulls.
Use Secrets and Environments
Never hardcode credentials. Use GitHub Secrets for sensitive data and environment variables for configuration:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
For different environments (staging, production), you can use GitHub Environments to add protection rules and secrets.
Advanced Techniques and Real-World Examples
Deploying to Kubernetes
If you're using Kubernetes, you can update deployments with the new image using kubectl:
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/myapp myapp=myregistry/myapp:${{ github.sha }}
kubectl rollout status deployment/myapp
Using GitHub Actions with Docker Compose
For multi-container applications, Docker Compose can be used in CI to set up the entire stack for integration tests:
- name: Run integration tests
run: |
docker-compose up -d
docker-compose run tests
Automating Versioning and Releases
You can automate semantic versioning using tools like semantic-release:
- name: Release
uses: cycjimmy/semantic-release-action@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
This will automatically bump version numbers and generate changelogs based on commit messages.
Security Considerations
Security is crucial in CI/CD. Here are some key practices:
- Use Official Images: Always use official or verified Docker images to avoid vulnerabilities.
- Scan Images: Use tools like Trivy or Snyk to scan images for vulnerabilities before deployment.
- Least Privilege: Grant only necessary permissions to workflows and use read-only tokens where possible.
- Pin Versions: Pin action versions to a full commit SHA to prevent supply chain attacks.
- Separate Environments: Use separate registries and secrets for production.
Example of scanning with Trivy:
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:latest
format: 'table'
exit-code: '1'
ignore-unfixed: true
severity: 'CRITICAL,HIGH'
Conclusion
Implementing modern CI/CD pipelines with GitHub Actions and Docker is a game-changer for software delivery. It automates repetitive tasks, ensures consistency, and accelerates time-to-market. By following the practices outlined in this article, you can build pipelines that are fast, reliable, and secure.
At Tanok Tech, we specialize in software development and AI consulting. Our team can help you design and implement CI/CD pipelines tailored to your specific needs, whether you're just starting or looking to optimize existing workflows.
Ready to transform your development process? Contact us today for a free consultation and let's build something great together.
Ready for the next step? Evaluate your company with our free checklist →
Download checklistRelated posts
- AI & ML◈
Apple Unveils 2026 AI Developer Tools: A New Era for On-Device Intelligence
Apple Unveils 2026 AI Developer Tools: A New Era for On-Device Intelligence
Sep 28, 2026
- AI & ML◈
The 7% Problem: Why Companies Are Bleeding Money on AI While Ignoring Their People
The 7% Problem: Why Companies Are Bleeding Money on AI While Ignoring Their People
Sep 27, 2026
- AI & ML◈
Babbage's Steam-Powered Dream: How a 3-Meter Mechanical Mind Foretold Modern AI
Babbage's Steam-Powered Dream: How a 3-Meter Mechanical Mind Foretold Modern AI
Sep 26, 2026