DEV Community

Roshan singh
Roshan singh

Posted on

# How to Build a Complete CI/CD Pipeline with GitHub Actions and Docker Hub

## Introduction

In this tutorial, I'll show you how to set up a complete CI/CD pipeline that automatically builds Docker images, pushes them to Docker Hub, and deploys your application to a production server - all triggered by a simple git push!

## What You'll Learn

  • Setting up GitHub Actions for automated builds
  • Building and pushing Docker images to Docker Hub
  • Deploying containerized applications to a remote server
  • Version tagging with Git commit SHAs
  • Zero-downtime deployments with Docker Compose

## Prerequisites

Before we start, make sure you have:

  • A GitHub repository with your application code
  • A Docker Hub account
  • A remote server (VPS/Cloud VM) with Docker and Docker Compose installed
  • SSH access to your server
  • Basic understanding of Docker and Docker Compose

## Architecture Overview

Our CI/CD pipeline consists of two main jobs:

  1. Build & Push: Build Docker images and push them to Docker Hub
  2. Deploy: Pull the new images on the server and restart containers
Git Push → GitHub Actions → Build Images → Docker Hub → Deploy to Server → Live!
Enter fullscreen mode Exit fullscreen mode

## Step 1: Project Structure

Here's what your project structure should look like:

your-project/
├── .github/
│   └── workflows/
│       └── deploy.yml          # CI/CD workflow
├── backend/
│   ├── Dockerfile              # Backend Docker image
│   └── ... (your backend code)
├── frontend/
│   ├── Dockerfile              # Frontend Docker image
│   └── ... (your frontend code)
├── docker-compose.prod.yml     # Production deployment config
└── nginx.conf                  # Nginx configuration
Enter fullscreen mode Exit fullscreen mode

## Step 2: Create Dockerfiles

### Backend Dockerfile Example

# backend/Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 5000

CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

*### Frontend Dockerfile Example
*

# frontend/Dockerfile
FROM node:18-alpine as build

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode


yaml

## Step 3: Create Docker Compose Production File

# docker-compose.prod.yml
version: '3.8'

services:
  backend:
    image: yourdockerhub/crm-backend:${CRM_BACKEND_TAG:-latest}
    container_name: crm-backend
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
    ports:
      - "5000:5000"
    networks:
      - app-network

  frontend:
    image: yourdockerhub/crm-frontend:${CRM_FRONTEND_TAG:-latest}
    container_name: crm-frontend
    restart: unless-stopped
    ports:
      - "3000:80"
    depends_on:
      - backend
    networks:
      - app-network

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - frontend
      - backend
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

## Step 4: Create GitHub Actions Workflow

Create .github/workflows/deploy.yml:

name: Build and Deploy to Production

on:
  push:
    branches: [main]

jobs:
  build-and-push:
    name: Build and push Docker images
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Generate image tags
        id: tag
        run: |
          echo "full=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
          echo "short=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"

      - name: Build and push backend image
        uses: docker/build-push-action@v5
        with:
          context: ./backend
          file: ./backend/Dockerfile
          push: true
          tags: |
            yourdockerhub/crm-backend:latest
            yourdockerhub/crm-backend:${{ steps.tag.outputs.full }}
            yourdockerhub/crm-backend:${{ steps.tag.outputs.short }}

      - name: Build and push frontend image
        uses: docker/build-push-action@v5
        with:
          context: ./frontend
          file: ./frontend/Dockerfile
          push: true
          tags: |
            yourdockerhub/crm-frontend:latest
            yourdockerhub/crm-frontend:${{ steps.tag.outputs.full }}
            yourdockerhub/crm-frontend:${{ steps.tag.outputs.short }}

  deploy:
    name: Deploy to production server
    runs-on: ubuntu-latest
    needs: build-and-push

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Copy configuration files to server
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          source: "docker-compose.prod.yml,nginx.conf"
          target: "~/crm-app"

      - name: Deploy application
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd ~/crm-app
            TAG="${{ github.sha }}"

            # Clean up old images
            sudo docker image prune -a -f

            # Pull new images
            sudo docker compose -f docker-compose.prod.yml pull backend frontend

            # Recreate containers with new images
            sudo CRM_BACKEND_TAG="$TAG" CRM_FRONTEND_TAG="$TAG" \
              docker compose -f docker-compose.prod.yml up -d \
              --force-recreate --no-deps backend frontend

            # Restart nginx
            sudo docker compose -f docker-compose.prod.yml restart nginx

            # Final cleanup
            sudo docker image prune -a -f
Enter fullscreen mode Exit fullscreen mode

## Step 5: Configure GitHub Secrets

Go to your GitHub repository → Settings → Secrets and variables → Actions

Add the following secrets:

  1. DOCKERHUB_USERNAME: Your Docker Hub username
  2. DOCKERHUB_TOKEN: Docker Hub access token
  3. SSH_HOST: Your server IP address or domain
  4. SSH_USER: SSH username
  5. SSH_KEY: Your private SSH key

## Conclusion

You now have a fully automated CI/CD pipeline! Every push to main will automatically build, test, and deploy your application.

Tags: #docker #cicd #githubactions #devops #automation

Questions? Drop them in the comments below! 👇

Top comments (0)