Docker CI/CD Guide
A practical guide to integrating Docker into your Continuous Integration and Deployment pipelines.
What is CI/CD?
CI/CD stands for Continuous Integration and Continuous Delivery (or Continuous Deployment). It is a modern software development practice that enables teams to deliver code changes more frequently and reliably through automation.
-
Continuous Integration (CI) is the practice of automatically building and testing code every time a team member commits changes to version control. This helps catch bugs early and ensures that the application is always in a deployable state.
-
Continuous Delivery (CD) automates the process of delivering code to staging or production environments after successful tests. With CD, code changes can be pushed to production safely and quickly.
-
Continuous Deployment takes CD a step further by automatically deploying every change that passes all stages of the pipeline to production without manual intervention.
Benefits of CI/CD
- Faster development cycles
- Higher software quality
- Reduced manual errors
- Quicker feedback and recovery
- Consistent and repeatable deployments
CI/CD is a cornerstone of DevOps practices, promoting collaboration between development and operations teams, and enabling organisations to respond to market changes faster.
Why Docker for CI/CD?
Docker simplifies CI/CD pipelines by offering:
- Consistent environments across build/test/deploy stages
- Isolation for dependencies and tools
- Portability across local, staging, and production
- Faster testing and deployment with containerised builds
CI/CD Workflow Overview
- Build Docker image from source
- Test application inside a container
- Push image to registry (Docker Hub, GitHub Container Registry, etc.)
- Deploy container to target environment
Prerequisites
- Docker installed locally or in your CI/CD environment
- A Dockerfile at the project root
- Docker Hub (or private registry) account
- GitHub/GitLab/CI tool of choice
Sample Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
CI Example with GitHub Actions
Create .github/workflows/docker.yml
:
name: Docker CI
on:
push:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: yourdockerhubuser/yourapp:latest
Save DockerHub credentials in GitHub repo Settings → Secrets as
DOCKER_USERNAME
andDOCKER_PASSWORD
.
CI Example with GitLab CI/CD
Create .gitlab-ci.yml
:
stages:
- build
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
deploy:
stage: deploy
script:
- echo "Deploy logic goes here (e.g. docker-compose up, SSH to server, etc.)"
GitLab will automatically set
$CI_REGISTRY
,$CI_REGISTRY_IMAGE
, and$CI_REGISTRY_USER
.
Deploy with Docker Compose
Use docker-compose.yml
on the target server:
version: '3'
services:
app:
image: yourdockerhubuser/yourapp:latest
ports:
- "80:3000"
restart: always
Deploy:
docker compose pull
docker compose up -d
Directory Structure
project/
├── Dockerfile
├── .dockerignore
├── .github/
│ └── workflows/
│ └── docker.yml
├── .gitlab-ci.yml
└── src/
Best Practices
- Pin image versions (
node:18-alpine
, notnode:latest
) - Use
.dockerignore
to keep builds small - Scan images for vulnerabilities (
docker scout quickview
) - Use secrets in CI environments, not hardcoded passwords
- Automate deployment with version tags
Bonus: Version Tagging
In your pipeline, you can tag builds by commit or semver:
docker tag yourapp:latest yourapp:v1.0.3
docker push yourapp:v1.0.3
Or dynamically in CI (example for GitHub):
tags: youruser/yourapp:latest, youruser/yourapp:${{ github.sha }}