Blog
Jan 28, 2025 - 7 MIN READ
GitHub Actions CI/CD for Laravel: A Zero-Downtime Deployment Setup

GitHub Actions CI/CD for Laravel: A Zero-Downtime Deployment Setup

A practical walkthrough of atomic symlink deployments, parallel test pipelines, and one-command rollbacks using GitHub Actions and DigitalOcean.

Vincent Ndegwa

Vincent Ndegwa

Every project I ship has a GitHub Actions pipeline. Not because it's fashionable, but because manually SSH-ing into a server and running git pull is a liability. Here's the exact setup I use for Laravel projects on DigitalOcean, with zero-downtime atomic deployments.

The Goal

  • Push to main → tests run → if green, deploy automatically
  • No downtime during deployment
  • Rollback in one command if something goes wrong

The Workflow File

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

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
      - run: composer install --no-interaction --prefer-dist
      - run: cp .env.testing .env && php artisan key:generate
      - run: php artisan test --parallel

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            cd /var/www/releases
            git clone git@github.com:you/app.git $(date +%Y%m%d%H%M%S)
            cd $(ls -t | head -1)
            composer install --no-dev --optimize-autoloader
            cp /var/www/shared/.env .env
            php artisan migrate --force
            php artisan config:cache && php artisan route:cache
            ln -sfn /var/www/releases/$(ls -t /var/www/releases | head -1) /var/www/current
            sudo systemctl reload php8.4-fpm
            # Keep only the last 5 releases
            ls -t /var/www/releases | tail -n +6 | xargs -I{} rm -rf /var/www/releases/{}

How Zero-Downtime Works

The key is the atomic symlink swap: Nginx serves /var/www/current, which is a symlink. The new release is fully built in a timestamped directory before the symlink is updated. The swap (ln -sfn) is a single atomic filesystem operation — requests in flight finish against the old release, new requests immediately use the new one.

Rollback

If something breaks:

# On the server — point current at the previous release
PREV=$(ls -t /var/www/releases | sed -n '2p')
ln -sfn /var/www/releases/$PREV /var/www/current
sudo systemctl reload php8.4-fpm

Done. Previous version is live in seconds.

Secrets Management

Never commit .env. The shared .env lives at /var/www/shared/.env on the server and is copied into each release. GitHub Secrets store the SSH key and host details — they're never in the repo.

I recently took on a project that challenged everything about my usual design process. A small literary journal wanted a digital platform that encouraged readers to slow down and engage deeply with content—the exact opposite of most websites optimized for quick consumption.

This got me thinking about what I'm calling "slow design"—an approach that intentionally creates space for contemplation rather than rapid interaction.

For the Wordsmith Journal, I experimented with subtle animations that respond to reading pace, typography that encourages focus, and navigation that reveals content gradually rather than all at once. The result feels more like turning pages in a physical book than scrolling through a typical website.

User testing revealed something fascinating: readers spent 3x longer with articles and reported higher satisfaction and better recall of content compared to the journal's previous site. By designing for attention rather than distraction, we created a digital experience that honors the thoughtful nature of the content itself.

I'm now incorporating elements of slow design into all my projects, asking: "Where can we create moments of pause? How can we reward attention rather than just capturing it?"

In our rush to optimize for engagement metrics, I think we've forgotten that sometimes the most meaningful digital experiences are the ones that don't demand immediate action but instead create space for thought.

Vincent Ndegwa • Full-Stack Engineer & AI Engineer © 2026