CLOUD
Guides

Building CI/CD Pipeline: From Code to Production Automatically

CLOUD|January 23, 2026|5 min read|600 views
Building CI/CD Pipeline: From Code to Production Automatically

Git push → automatic tests → deploy to server. GitHub Actions or GitLab CI. We explain the complete setup.

What is CI/CD?

CI/CD consists of two stages:

  • CI (Continuous Integration): Automatically integrating code — every developer pushes code, it gets built and tested automatically
  • CD (Continuous Deployment/Delivery): Automatically deploying code to the server after tests pass

Without CI/CD, deployment is manual: pull code, install dependencies, run migrations, clear cache — and every step is an opportunity for human error. With CI/CD, the entire process happens with a single git push.

GitHub Actions: Getting Started

GitHub Actions is the easiest option if your code is on GitHub — integrated with the repository and free within reasonable limits.

Directory Structure

.github/
└── workflows/
 ├── ci.yml # Tests and build
 └── deploy.yml # Deploy to server

CI File: Automated Tests

# File: .github/workflows/ci.yml
name: CI

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

jobs:
 tests:
 runs-on: ubuntu-latest

 services:
 mysql:
 image: mysql:8.0
 env:
 MYSQL_ROOT_PASSWORD: password
 MYSQL_DATABASE: testing
 ports:
 - 3306:3306
 options: >-
 --health-cmd="mysqladmin ping"
 --health-interval=10s
 --health-timeout=5s
 --health-retries=3

 redis:
 image: redis:7
 ports:
 - 6379:6379

 steps:
 - uses: actions/checkout@v4

 - name: Setup PHP
 uses: shivammathur/setup-php@v2
 with:
 php-version: '8.3'
 extensions: mbstring, xml, ctype, json, curl, mysql, redis
 coverage: xdebug

 - name: Install Composer Dependencies
 run: composer install --no-progress --prefer-dist --optimize-autoloader

 - name: Setup Node.js
 uses: actions/setup-node@v4
 with:
 node-version: '20'
 cache: 'npm'

 - name: Install NPM Dependencies
 run: npm ci

 - name: Build Assets
 run: npm run build

 - name: Prepare Environment
 run: |
 cp .env.ci .env
 php artisan key:generate

 - name: Run Migrations
 run: php artisan migrate --force
 env:
 DB_HOST: 127.0.0.1
 DB_DATABASE: testing
 DB_USERNAME: root
 DB_PASSWORD: password

 - name: Run Tests
 run: php artisan test --parallel
 env:
 DB_HOST: 127.0.0.1
 DB_DATABASE: testing
 DB_USERNAME: root
 DB_PASSWORD: password

 - name: Run Static Analysis
 run: ./vendor/bin/phpstan analyse --memory-limit=2G

CD File: Automated Deployment

# File: .github/workflows/deploy.yml
name: Deploy

on:
 push:
 branches: [main]

jobs:
 deploy:
 runs-on: ubuntu-latest
 if: github.ref == 'refs/heads/main'

 steps:
 - uses: actions/checkout@v4

 - name: Deploy to Server
 uses: appleboy/ssh-action@v1
 with:
 host: ${{ secrets.SERVER_HOST }}
 username: ${{ secrets.SERVER_USER }}
 key: ${{ secrets.SSH_PRIVATE_KEY }}
 script: |
 cd /var/www/app
 git pull origin main
 composer install --no-dev --optimize-autoloader
 php artisan migrate --force
 php artisan optimize:clear
 php artisan optimize
 php artisan queue:restart
 npm ci
 npm run build
 sudo systemctl reload php8.3-fpm

GitLab CI/CD

If you use GitLab:

# File: .gitlab-ci.yml
stages:
 - test
 - build
 - deploy

variables:
 MYSQL_ROOT_PASSWORD: password
 MYSQL_DATABASE: testing

test:
 stage: test
 image: php:8.3
 services:
 - mysql:8.0
 - redis:7
 before_script:
 - apt-get update && apt-get install -y git zip unzip
 - docker-php-ext-install pdo pdo_mysql
 - curl -sS https://getcomposer.org/installer | php
 - php composer.phar install
 - cp .env.ci .env
 - php artisan key:generate
 - php artisan migrate
 script:
 - php artisan test

deploy:
 stage: deploy
 only:
 - main
 before_script:
 - apt-get update && apt-get install -y openssh-client
 - eval $(ssh-agent -s)
 - echo "$SSH_PRIVATE_KEY" | ssh-add -
 script:
 - ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_HOST "cd /var/www/app && git pull && composer install --no-dev && php artisan migrate --force && php artisan optimize"

Advanced Deployment Script

# File: scripts/deploy.sh (on the server)
#!/bin/bash
set -e

APP_DIR="/var/www/app"
BACKUP_DIR="/opt/backups/releases"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "[$TIMESTAMP] Starting deployment..."

# Quick database backup
echo "Creating backup..."
mysqldump -u root app_db | gzip > "$BACKUP_DIR/db_$TIMESTAMP.sql.gz"

cd $APP_DIR

# Enable maintenance mode
php artisan down --retry=60

# Pull code
echo "Pulling updates..."
git pull origin main

# Install dependencies
echo "Installing dependencies..."
composer install --no-dev --optimize-autoloader --no-interaction
npm ci
npm run build

# Migrations
echo "Running migrations..."
php artisan migrate --force

# Cache
echo "Optimizing..."
php artisan optimize:clear
php artisan optimize
php artisan view:cache
php artisan event:cache

# Restart services
echo "Restarting services..."
php artisan queue:restart
sudo systemctl reload php8.3-fpm
sudo systemctl reload nginx

# Disable maintenance mode
php artisan up

echo "[$TIMESTAMP] Deployment completed successfully."

The .env.ci File

# File: .env.ci
APP_NAME="MyApp"
APP_ENV=testing
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=root
DB_PASSWORD=password

CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=sync

REDIS_HOST=127.0.0.1
REDIS_PORT=6379

Setting Up Secrets in GitHub

# In repository settings > Secrets and variables > Actions
# Add:
SERVER_HOST → Server IP address
SERVER_USER → Username (deploy)
SSH_PRIVATE_KEY → SSH private key

Creating an SSH Key for Deployment

# On your local machine
ssh-keygen -t ed25519 -f deploy_key -C "github-actions-deploy"

# Copy the public key to the server
ssh-copy-id -i deploy_key.pub deploy@your-server-ip

# Copy the private key to GitHub Secrets
cat deploy_key # Copy the entire content

Best Practices

  • Never deploy from the develop branch directly — merge into main first
  • Tests are mandatory before any deployment
  • Backup automatically before every deployment
  • Maintenance mode during deployment
  • Rollback — keep the last 5 releases for quick rollback
  • Notifications — send alerts on deployment success or failure

CI/CD with CLOUD

CLOUD (cloud.net.sa) servers support automated deployment via SSH without any restrictions. Set up an SSH key in GitHub Actions and connect it to your server in Riyadh. Every git push to main reaches your server within seconds.

With very low latency and fast SSD storage, the build and deploy process on CLOUD completes fast enough that you never need to wait.


Get Started

Ready to build your project? Configure your cloud server with the specs your app needs, or view plans and pricing.

Share