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

  1. Navigate to the admin panel at http://your-server-ip:8080/admin.
  2. 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.
  3. Configure the site title and basic settings in Admin → Configuration → Site.
  4. 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:

SettingDefaultDescription
pages.themequarkActive theme
cache.enabledtruePage caching (disable for development)
languages.supported[]Multi-language support
debugger.enabledfalseDebug toolbar
images.default_image_quality85JPEG 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.

Comments