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.
