How to Self-Host Grav CMS with Docker Compose
What Is Grav?
Grav is a flat-file CMS built on PHP that stores everything — pages, configuration, user accounts, media — as files on disk instead of in a database. No MySQL, no PostgreSQL, no database maintenance. Your entire site lives in a single directory that you can back up by copying it.
Grav uses Markdown for content, YAML for configuration, and Twig for templating. It includes a built-in admin panel with a visual editor, a package manager (GPM) for installing plugins and themes, and multi-language support. It’s lighter and simpler than WordPress while being more feature-rich than static site generators like Hugo.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 512 MB of RAM (256 MB minimum for small sites)
- A domain name (optional, for remote access)
Docker Compose Configuration
Grav is a single-container application with no database dependency. The LinuxServer.io image includes PHP, Nginx, and the Grav Admin plugin pre-installed.
Create a docker-compose.yml file:
services:
grav:
image: lscr.io/linuxserver/grav:1.7.49.5
container_name: grav
restart: unless-stopped
environment:
- PUID=1000 # User ID for file permissions
- PGID=1000 # Group ID for file permissions
- TZ=Etc/UTC # Your timezone (e.g., America/New_York)
volumes:
- grav-config:/config
ports:
- "8080:80"
volumes:
grav-config:
Start the stack:
docker compose up -d
Access Grav at http://your-server-ip:8080.
Initial Setup
- Navigate to the admin panel at
http://your-server-ip:8080/admin. - Create your admin account. On first access, Grav prompts you to set up the initial administrator. Choose a strong password — there’s no password recovery without file access.
- Configure the site title and basic settings in Admin → Configuration → Site.
- Choose a theme in Admin → Themes. The default Quark theme is clean and responsive, but hundreds of alternatives are available via GPM.
Alternative: Create Admin via CLI
If the web-based admin creation doesn’t appear:
docker exec -it grav bin/plugin login new-user
Follow the prompts to create a username, password, email, and set the user’s permissions to admin.
Configuration
Site Configuration
Grav stores all configuration in YAML files under /config/user/config/. The main settings live in system.yaml and site.yaml.
Edit via the admin panel (Configuration → System/Site) or directly in the files:
# Access the configuration directory
docker exec -it grav ls /config/user/config/
Key settings in system.yaml:
| Setting | Default | Description |
|---|---|---|
pages.theme | quark | Active theme |
cache.enabled | true | Page caching (disable for development) |
languages.supported | [] | Multi-language support |
debugger.enabled | false | Debug toolbar |
images.default_image_quality | 85 | JPEG quality for resized images |
Installing Plugins
Use the admin panel (Plugins → Add) or the CLI:
# Install a plugin via GPM
docker exec -it grav bin/gpm install shortcode-core
# Popular plugins:
# - admin (pre-installed)
# - sitemap — generates XML sitemap
# - feed — generates RSS/Atom feeds
# - pagination — page list pagination
# - breadcrumbs — navigation breadcrumbs
# - markdown-notices — callout boxes in Markdown
Installing Themes
docker exec -it grav bin/gpm install theme-name
Activate a new theme via Admin → Themes or by editing system.yaml:
pages:
theme: theme-name
Advanced Configuration (Optional)
Email Configuration
For contact forms and password resets, configure SMTP in the Email plugin settings (Admin → Plugins → Email):
# In user/config/plugins/email.yaml
enabled: true
from: [email protected]
mailer:
engine: smtp
smtp:
server: smtp.example.com
port: 587
encryption: tls
user: [email protected]
password: your-password
Multi-Language Support
Grav supports multiple languages natively. Enable in system.yaml:
languages:
supported:
- en
- es
- fr
default_lang: en
include_default_lang: true
Create translated pages by adding language suffixes: default.en.md, default.es.md, default.fr.md.
Custom Page Types
Grav’s page structure is file-based. Create new content types by adding Twig templates to your theme:
user/themes/quark/templates/
├── default.html.twig # Standard pages
├── blog.html.twig # Blog listing
├── blog_item.html.twig # Blog posts
├── modular.html.twig # Modular pages
└── portfolio.html.twig # Custom type
Reverse Proxy
The container runs Nginx on port 80 (mapped to 8080 in the example above). For HTTPS in production, use a reverse proxy.
For Nginx Proxy Manager, create a proxy host pointing to port 8080 with SSL enabled.
CloudFlare users: Disable Rocket Loader globally or for /admin paths — it breaks the admin panel’s JavaScript scrolling.
See Reverse Proxy Setup for detailed instructions.
Backup
Grav’s flat-file architecture makes backups simple. Everything lives in the /config volume:
# Built-in backup command
docker exec -it grav bin/grav backup
# Or back up the entire volume
docker run --rm -v grav-config:/data -v $(pwd):/backup alpine tar czf /backup/grav-backup.tar.gz -C /data .
Restore by extracting the archive back to the volume. No database dumps or restoration procedures — just files.
See Backup Strategy for automated approaches.
Troubleshooting
Admin Panel Not Loading
Symptom: /admin returns a 404 or blank page.
Fix: The admin plugin may not be installed. Run: docker exec -it grav bin/gpm install admin. If GPM can’t connect (SSL issues behind a proxy), check system.gpm.verify_peer in configuration.
GPM “Fetch Failed” Errors
Symptom: Plugin/theme installation fails with network errors.
Fix: GPM requires outbound HTTPS access to getgrav.org. If behind a corporate firewall or restrictive network, ensure port 443 outbound is open. Alternatively, download plugins manually from the Grav website and extract them to /config/user/plugins/.
File Permission Errors
Symptom: “Unable to write file” or “Permission denied” in logs.
Fix: Check that PUID and PGID environment variables match your host user. The container runs as this user for file operations. Update the values and recreate the container: docker compose up -d --force-recreate grav
Admin Panel Scrolling Broken
Symptom: Admin panel content doesn’t scroll on some pages. Fix: If using CloudFlare, disable Rocket Loader (Speed → Optimization → Rocket Loader). Clear your browser cache. This is a known conflict with Grav’s admin JavaScript.
Slow Page Loads
Symptom: Pages take 2+ seconds to load. Fix: Enable caching in Admin → Configuration → System → Caching → Enabled. Grav compiles Twig templates and caches processed Markdown. First load after clearing cache is slow; subsequent loads should be under 200ms.
Resource Requirements
- RAM: 256 MB idle, 512 MB under load with admin panel active
- CPU: Low (single core sufficient for small to medium sites)
- Disk: 100 MB for Grav core + plugins + themes, plus your content and media
Verdict
Grav is the best flat-file CMS for self-hosting. The zero-database architecture eliminates an entire category of maintenance, backup complexity, and failure modes. The admin panel provides a user-friendly editing experience, and GPM handles plugin/theme management cleanly.
Choose Grav over WordPress if you want simplicity and don’t need WordPress’s massive plugin ecosystem. Choose Grav over Hugo if you want an admin panel and dynamic features without static site generation. For database-backed CMS power with more features, stick with WordPress. For pure speed and simplicity with no server-side processing, choose Hugo.
Related
- Grav vs Hugo: Flat-File CMS or Static Generator?
- Grav vs October CMS: PHP Content Systems Compared
- Ghost vs WordPress: Which Should You Self-Host?
- How to Self-Host WordPress
- How to Self-Host Ghost
- How to Self-Host Hugo
- Best Self-Hosted CMS
- Self-Hosted Alternatives to WordPress.com
- Docker Compose Basics
- Reverse Proxy Setup
- Backup Strategy
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments