Building a Git-based CMS Workflow for Laravel

· 3 min read

The traditional CMS pattern involves a database, an admin panel, a WYSIWYG editor, and user authentication. It’s heavy.

For a developer blog, the Git-based CMS pattern is superior.

  1. Drafting: Write locally in VS Code (or Obsidian/Notion).
  2. Versioning: Git handles history, diffs, and backups.
  3. Publishing: git push origin main.
  4. Deployment: Automated webhook pulls changes.

Here is how to set this up for a Laravel application.

1. The Content Repository

You have two choices:

  1. Monorepo: Keep content inside the Laravel content/ folder (Simpler, what this blog uses).
  2. Content Repo: A separate repo just for Markdown files (Better for separation of concerns).

If you use a separate repo, you can add it as a Git Submodule or clone it during deployment.

2. The Deployment Script

You don't need complex CI/CD for a personal blog. A simple Laravel Envoy script or a bash script works wonders.

Here is a simple robust bash strategy (deploy.sh):

#!/bin/bash
set -e

echo "🚀 Starting deployment..."

# 1. Pull latest code
git pull origin main

# 2. Install dependencies (if composer.lock changed)
composer install --no-dev --optimize-autoloader

# 3. Build assets (if designing)
npm run build

# 4. Clear and Cache Config
php artisan config:cache
php artisan route:cache
php artisan view:cache

# 5. Clear Application Cache (Critical for file-based blogs)
php artisan cache:clear

echo "✅ Deployment finished!"

3. Automating with Webhooks

To make this "Git Push = Publish", you need a webhook listener.

GitHub Actions (Push-Model)

Create a workflow file .github/workflows/deploy.yml:

name: Deploy
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/my-blog
            ./deploy.sh

Webhook Route (Pull-Model)

Alternatively, create a hidden route in Laravel that GitHub calls.

// routes/api.php
Route::post('/deploy-webhook', function (Request $request) {
    $signature = $request->header('X-Hub-Signature-256');
    // Verify signature logic here...
    
    // Trigger deployment process (e.g., dispatch a Job)
    \App\Jobs\DeploySite::dispatch();
    
    return response()->json(['status' => 'deploying']);
});

Security Note: Always verify the GitHub webhook signature secret!

4. Handling Cache Invalidation

Since our blog uses file-based caching (parsing Markdown is expensive), the cache must be cleared when content updates.

In our simpler setup, php artisan cache:clear in the deployment script is a sledgehammer approach that works fine for small blogs.

For larger sites, you might want to only clear specific keys. You can listen for file changes if using a daemon, but that's over-engineering.

5. The Writing Workflow

My personal workflow looks like this:

  1. Create a branch post/new-feature.
  2. Write content/posts/YYYY-MM-DD-feature.md.
  3. Set draft: true in frontmatter while working.
  4. Run php artisan serve to preview locally.
  5. Commit and Push.
  6. Merge to main when ready to go live.

Advantages

  • Zero Admin Panel: Less code to maintain, smaller attack surface.
  • Offline Writing: Write on a plane, commit later.
  • Ownership: Your content is in flat files, readable 50 years from now.
  • Speed: No database queries to render a post (if cached properly).

This architecture brings the "Developer Experience" to blogging.

Comments