Grav Git Workflow — Git-Based Deployment, CI/CD and Multi-Environment
In this tutorial, you'll learn Grav Git workflow — setting up Git-based deployment pipelines, integrating with CI/CD services, creating multi-environment workflows (development, staging, production), and managing team collaboration with flat-file content.
What You'll Learn
- Why Git is a natural fit for Grav's flat-file architecture
- Setting up a Git repository for a Grav site
- Multi-environment configuration (dev, staging, prod)
- Git-based deployment strategies
- CI/CD integration with GitHub Actions
- Team collaboration workflows with Git
Why It Matters
In WordPress, Git workflows are complex because content is in a database. You need plugins to sync the database, and deployment involves both files and SQL dumps. In Grav, everything is files — pages, configuration, plugins, themes. This means your entire site can live in Git. Branch, merge, pull request, and deploy just like any software project. This is the modern way to manage a CMS.
Real-World Use
A team of 5 developers and 3 content editors works on a documentation site. Developers create branches for new features. Content editors create branches for new articles. Pull requests are reviewed and merged. On merge to main, a GitHub Action deploys the site to production. The entire workflow is Git-based — no database syncs, no Migration scripts, no manual file transfers.
Learning Path
flowchart LR
A["Security"] --> B["Git Workflow
← You are here"]:::current
B --> C["CLI Tools"]
C --> D["Production Deployment"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Why Git + Grav Works
Grav's flat-file structure maps directly to Git's file-based model:
grav-site/
├── user/
│ ├── pages/ # Content pages (Markdown)
│ ├── config/ # Site configuration (YAML)
│ ├── plugins/ # Plugin code (PHP, YAML)
│ ├── themes/ # Theme code (Twig, CSS, JS)
│ ├── accounts/ # User accounts (YAML)
│ └── languages/ # Translations (YAML)
├── .gitignore
└── README.md
Every change is a file change. Every file change is tracked by Git.
Setting Up Git
cd /var/www/grav-site
git init
git add .
git commit -m "Initial Grav site"
# Add remote
git remote add origin git@github.com:yourorg/grav-site.git
git push -u origin main
.gitignore
Create .gitignore:
# Grav generated files
user/data/cache/*
user/data/images/*
user/data/tmp/*
user/data/logs/*
!user/data/logs/.gitkeep
# User configuration (contains secrets)
user/config/system.yaml
user/config/site.yaml
user/config/themes/*.yaml
user/config/plugins/*.yaml
# Environment-specific
.env
.env.local
# OS files
.DS_Store
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
Tracking Config Securely
Use .env for secrets and a template for config:
# Create config templates
cp user/config/system.yaml user/config/system.yaml.example
cp user/config/site.yaml user/config/site.yaml.example
# Track the templates, not the actual config
git add user/config/system.yaml.example
git add user/config/site.yaml.example
Multi-Environment Setup
Directory Structure
grav-site/
├── .env # Environment variables (not tracked)
├── .env.example # Environment template (tracked)
├── bootstrap.php # Environment loader
├── user/
│ └── config/
│ └── environments/ # Environment-specific config
│ ├── dev/
│ │ └── system.yaml
│ ├── staging/
│ │ └── system.yaml
│ └── prod/
│ └── system.yaml
Environment Configuration
user/config/environments/dev/system.yaml:
cache:
enabled: false
check:
pages: true
yaml: true
twig: true
twig:
cache: false
debug: true
auto_reload: true
debugger:
enabled: true
user/config/environments/prod/system.yaml:
cache:
enabled: true
driver: redis
lifetime: 604800
check:
yaml: false
twig: false
twig:
cache: true
auto_reload: false
debugger:
enabled: false
Environment Detection
// bootstrap.php
$env = getenv('GRAV_ENV') ?: 'prod';
switch ($env) {
case 'dev':
case 'development':
$grav['environment'] = 'dev';
break;
case 'staging':
$grav['environment'] = 'staging';
break;
default:
$grav['environment'] = 'prod';
}
Git-Based Deployment
GitHub Actions Workflow
.github/workflows/deploy.yml:
name: Deploy Grav Site
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: curl, mbstring, xml, zip, gd
- name: Install dependencies
run: |
curl -sS https://getcomposer.org/installer | php
php composer.phar install --no-dev --optimize-autoloader
- name: Build assets
run: |
php bin/grav cache --clear
php bin/grav install
- name: Warm cache
run: php bin/grav cache:warmup
- name: Deploy to production
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
source: "."
target: "/var/www/grav-site"
strip_components: 0
- name: Post-deploy
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /var/www/grav-site
php bin/grav cache --clear
chmod -R 755 .
chmod -R 775 user/data user/accounts user/config
php bin/grav cache:warmup
Team Collaboration Workflow
Branch Strategy
main → Production-ready code
├── develop → Integration branch
│ ├── feature/new-article → Content changes
│ ├── feature/new-template → Theme changes
│ ├── fix/broken-link → Bug fixes
│ └── chore/update-plugins → Maintenance
Content Editor Workflow
# Content editor creates a new article
git checkout -b feature/new-guide
# Create the page folder
mkdir -p user/pages/06.guides/01.new-guide
# Create the Markdown file
echo "---
title: New Guide
---
Content here..." > user/pages/06.guides/01.new-guide/default.md
# Commit and push
git add .
git commit -m "Add new guide: how to use Grav"
git push -u origin feature/new-guide
Then create a pull request on GitHub. After review, merge to main.
Deployment Script
deploy.sh:
#!/bin/bash
set -e
echo "Deploying Grav site..."
# Pull latest code
git pull origin main
# Install dependencies
composer install --no-dev --optimize-autoloader
# Set production environment
export GRAV_ENV=prod
# Clear cache
php bin/grav cache --clear
# Install plugins/themes
php bin/grav install
# Set permissions
chmod -R 755 .
chmod -R 775 user/data
chmod -R 775 user/accounts
chmod -R 775 user/config
# Warm cache
php bin/grav cache:warmup
echo "Deployment complete!"
Learning Path
flowchart LR
A["Security"] --> B["Git Workflow
← You are here"]:::current
B --> C["CLI Tools"]
C --> D["Production Deployment"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Common Mistakes
Tracking secrets in Git: Never commit
system.yaml,site.yaml, or any file containing passwords, API keys, or database credentials. Use.envfiles and template configs.Not ignoring cache directories: Cache files change constantly and should never be in Git. Add
user/data/cache/*,user/data/images/*, anduser/data/tmp/*to.gitignore.Merging without testing: Always test changes in a staging environment before merging to production. A broken template or config error can take down the live site.
No environment-specific configuration: Using the same cache settings in development (where you want no cache) and production (where you want aggressive cache) causes problems. Use environment-specific config.
Forgetting dependencies: The
vendor/directory should be in.gitignore. Runcomposer installduring deployment. Team members need to run it after cloning.
Practice Questions
Why is Grav a natural fit for Git-based workflows? Answer: Grav stores all content, configuration, and code as files. Unlike database-driven CMSs, there is no database to sync. The entire site can be version-controlled with Git.
What files should be in
.gitignorefor a Grav project? Answer: Cache files (user/data/cache/*,user/data/images/*,user/data/tmp/*), actual config files containing secrets (system.yaml,site.yaml), thevendor/directory, and environment files (.env).How do you manage environment-specific configuration? Answer: Use an environment loader (like
Bootstrap.php) that detects the active environment and loads configuration fromuser/config/environments/{env}/.What is the recommended branch strategy for a Grav team? Answer: Main branch for production-ready code, develop for integration, feature branches for new content or features, and fix branches for bug fixes. Use pull requests for review.
Challenge: Set up a complete Git workflow for a Grav site with 3 environments. Create the repository with proper .gitignore and config templates. Set up GitHub Actions for CI/CD that: runs on push to main, installs dependencies, warms the cache, deploys to production via SSH, and runs post-deployment tasks. Create environment-specific configuration for dev (no cache, debug on) and prod (Redis cache, debug off). Add a staging environment that deploys on push to the develop branch. Include a content editor workflow with branches, pull requests, and automated preview deployments.
FAQ
Mini Project
Goal: Set up a complete Git-based deployment pipeline for Grav.
- Initialize a Git repository with proper .gitignore
- Create config templates (system.yaml.example, site.yaml.example)
- Set up environment-specific configuration (dev, staging, prod)
- Create a GitHub Actions workflow for CI/CD
- Set up a staging environment for testing
- Create branch strategy documentation
- Implement post-deployment cache warming
- Set up environment detection in bootstrap.php
- Create a deployment script with proper permissions
- Test the full workflow: create branch, make changes, PR, merge, auto-deploy
What's Next
Now you have a modern Git-based workflow. Next, learn CLI tools:
Continue to Lesson 39: CLI Tools — bin/grav commands, automated tasks, and maintenance operations.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro