Skip to content

Dockerizing Applications and PR CI CD Setup

Njoku Ujunwa Sophia edited this page Aug 28, 2024 · 2 revisions

Introduction

This documentation provides a comprehensive guide for Dockerizing applications and configuring CI/CD pipelines using GitHub Actions. It covers the steps for containerizing backend and frontend applications, setting up environment-specific Docker Compose files, and automating deployments with GitHub Actions.

Dockerize Applications

To containerize your backend and frontend applications and set up Docker Compose for seamless development and deployment.

  • Backend Dockerfile: Define the environment and instructions for building the backend container.
  • Frontend Dockerfile: Define the environment and instructions for building the frontend container.

Backend Dockerfile

FROM php:8.2-fpm

RUN apt-get update && apt-get install -y \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libzip-dev \
    libpq-dev \
    git \
    unzip \
    && rm -rf /var/lib/apt/lists/*

RUN pecl install apcu \
    && docker-php-ext-enable apcu

RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install gd zip pdo pdo_pgsql

COPY php.ini /usr/local/etc/php/conf.d/custom.ini

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

COPY . /var/www/html

RUN composer install --no-interaction --prefer-dist --optimize-autoloader

RUN cp .env.example .env

RUN echo "apc.enable_cli=1" >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini

CMD ["sh", "-c", "nohup php artisan serve --host=0.0.0.0 "]

Frontend Dockerfile:

FROM node:20-alpine AS base

WORKDIR /app

COPY . .

RUN npm install -g pnpm 

RUN pnpm install

RUN pnpm run build

EXPOSE 3000

CMD ["node", "server.js"]


Create Docker Compose File:

Define a docker-compose.yml file at the root of your project to orchestrate services including backend, frontend, databases, proxy, and Redis.


services:
  app:
    build:
      context: .
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      - APP_ENV=production
      - APP_DEBUG=false
      - DB_CONNECTION=pgsql
      - DB_HOST=db
      - DB_PORT=5432
      - DB_DATABASE=app
      - DB_USERNAME=app
      - DB_PASSWORD=changethis123
      - QUEUE_CONNECTION=redis
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: changethis123
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 5s
  proxy:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: always
    ports:
      - '8090:81'
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./certs:/etc/nginx/letsencrypt
      - /var/run/docker.sock:/tmp/docker.sock:ro

  redis:
    image: redis:latest
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      retries: 3
      start_period: 30s
      timeout: 5s

  redis-commander:
    image: rediscommander/redis-commander:latest
    ports:
      - "8081:8081"
    environment:
      - REDIS_HOSTS=local:redis:6379

  adminer:
    image: adminer:latest
    ports:
      - "8080:8080"
    environment:
        ADMINER_DEFAULT_SEVERAL: db

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./fastcgi-php.conf:/etc/nginx/snippets/fastcgi-php.conf
    depends_on:
      - "app"
volumes:
  db_data:

Ensure services communicate properly with each other.


Set Up Environment-Specific Docker Compose Files

These will manage different configurations for staging and production environments by using environment-specific Docker Compose files.

  • Create docker-compose.staging.yml Configure services and settings tailored for the staging environment.

  • Create docker-compose.production.yml Configure services and settings tailored for the production environment.

###Docker-compose.staging.yml:

version: '3.8'

services:
  app-delve:
    image: delve_be:latest
    ports:
      - "8000:8000"
    depends_on:
      db-delve:
        condition: service_healthy
      redis-delve:
        condition: service_healthy
    environment:
      - APP_ENV=staging
      - APP_DEBUG=true
      - DB_CONNECTION=pgsql
      - DB_HOST=db
      - DB_PORT=5432
      - DB_DATABASE=app_staging
      - DB_USERNAME=app_staging
      - DB_PASSWORD=changethis123
      - QUEUE_CONNECTION=redis
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - WWWGROUP=1000
      - WWWUSER=1000

  db-delve:
    image: postgres:latest
    environment:
      POSTGRES_USER: app_staging
      POSTGRES_PASSWORD: changethis123
      POSTGRES_DB: app_staging
    ports:
      - "5400:5432"
    volumes:
      - db-delve_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_staging"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 5s
      
  redis-delve:
    image: redis:latest
    ports:
      - "6374:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      retries: 3
      start_period: 30s
      timeout: 5s
  adminer:
    image: adminer:latest
    ports:
      - "8080:8080"
    environment:
        ADMINER_DEFAULT_SEVERAL: db

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./fastcgi-php.conf:/etc/nginx/snippets/fastcgi-php.conf
    depends_on:
      - "app"
  proxy:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: always
    ports:
      - '8090:81'
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./certs:/etc/nginx/letsencrypt
      - /var/run/docker.sock:/tmp/docker.sock:ro

volumes:
  db-delve_data:

Docker-compose.production.yml:


version: '3.8'

services:
  app:
    build:
      context: .
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      - APP_ENV=production
      - APP_DEBUG=false
      - DB_CONNECTION=pgsql
      - DB_HOST=db
      - DB_PORT=5432
      - DB_DATABASE=app
      - DB_USERNAME=app
      - DB_PASSWORD=changethis123
      - QUEUE_CONNECTION=redis
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: changethis123
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 5s

  proxy:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: always
    ports:
      - '8090:81'
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./certs:/etc/nginx/letsencrypt
      - /var/run/docker.sock:/tmp/docker.sock:ro

  redis:
    image: redis:latest
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      retries: 3
      start_period: 30s
      timeout: 5s

  redis-commander:
    image: rediscommander/redis-commander:latest
    ports:
      - "8081:8081"
    environment:
      - REDIS_HOSTS=local:redis:6379

  adminer:
    image: adminer:latest
    ports:
      - "8080:8080"
    environment:
        ADMINER_DEFAULT_SEVERAL: db

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./fastcgi-php.conf:/etc/nginx/snippets/fastcgi-php.conf
    depends_on:
      - "app"
volumes:
  db_data:


Configure CI/CD with GitHub Actions

Automate Pull Request deployment using GitHub Actions and Docker. Use HNG project's pr-deploy GitHub Action: Follow instructions from the https://github.com/hngprojects/pr-deploy to set up pull request deployments. Ensure the action is configured to deploy previews for pull requests. In your GitHub Actions workflow, build Docker images for your application.

This workflow below will deploy on push or pull request.

  • On push, it will build the image, Use scp to upload the built images to the server, then docker load the image and run it using the appropriate docker-compose file .

Workflow


name: PR and DevOps Deploy

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]
  push:
    branches:
      - devops

jobs:
  build_and_deploy:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v2

      - name: Build Docker Image
        run: |
          IMAGE_TAG="latest"
          docker buildx build --tag delve_be:${IMAGE_TAG} --file Dockerfile .
      - name: List Docker Images
        run: docker images

      - name: Save Docker Image
        run: |
          IMAGE_TAG="latest"
          docker save delve_be:${IMAGE_TAG} -o delve_be.tar
      - name: Upload Docker Image Artifact
        uses: actions/upload-artifact@v2
        with:
          name: docker-image
          path: delve_be.tar

  deploy_to_server:
    if: github.event_name == 'push'
    needs: build_and_deploy
    runs-on: ubuntu-latest
    steps:
      - name: Download Docker Image Artifact
        uses: actions/download-artifact@v2
        with:
          name: docker-image
          path: .

      - name: Transfer Docker Image to Server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          source: delve_be.tar
          target: /tmp/php

      - name: Load and Deploy Docker Image on Server
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            IMAGE_TAG="latest"
            cd /tmp/php
            docker load < delve_be.tar
            rm -f delve_be-${IMAGE_TAG}.tar
            cd /var/www/langlearnai-be/pr
            git pull origin devops
            docker-compose -f docker-compose.staging.yml down
            docker-compose -f docker-compose.staging.yml up -d
  deploy-pr:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v2

      - id: deploy
        name: Pull Request Deploy
        uses: hngprojects/pr-deploy@dev
        with:
          server_host: ${{ secrets.SSH_HOST }}
          server_username: ${{ secrets.SSH_USER }}
          server_password: ${{ secrets.SSH_PASSWORD }}
          comment: true
          context: '.'
          dockerfile: 'Dockerfile'
          exposed_port: '7001'
          github_token: ${{ secrets.TOKEN }}

      - name: Print Preview Url
        run: |
          echo "Preview Url: ${{ steps.deploy.outputs.preview-url }}" 


By following these steps, you will be able to Dockerize your applications and managing deployments across different environments using GitHub Actions.