Files
intotheeast-com/docs/guides/local-setup.md
m038andClaude Opus 4.8 0f6b1e69cd docs: capture local Grav core upgrade + refresh version-authority docs
New learning: docs/solutions/tooling-decisions/upgrade-local-grav-core-rebuild-docker-image.md
— the local Grav core is baked into the Docker image (only ./user is bind-mounted),
so it upgrades by a Dockerfile URL bump + image rebuild + `docker rm -f` recreate,
not the `gpm self-upgrade` the servers use (non-durable in-container).

Refreshed three docs this exposed as stale/incomplete:
- local-setup.md: rewrote the stale "newer Grav RC" section with the durable
  rebuild procedure (recreate gotcha, verify, plugin refresh, non-durability note).
- deploy-cycle.md: Phase 0 now upgrades the local core; state-model notes the
  image as a fourth surface beyond the three server layers.
- stale-grav-version-blocks-api-plugin-install.md: version-authority surfaces
  3 -> 4 (hardcoded Dockerfile URL); clarified .env* GRAV_VERSION governs fresh
  remote installs only, never the local Docker core.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 12:05:51 +02:00

3.8 KiB

Local Development Setup

This guide covers setting up the dev environment from scratch after cloning the repo.


First-time setup

user/plugins/ and user/data/ are excluded from git but Grav requires them to exist. Create them once:

mkdir -p user/plugins user/data

Then run:

make setup

make setup = build → start → install-plugins → fix-perms. This builds the Docker image (Grav 2.0 baked in), starts the container, installs all plugins from plugins.txt, and fixes file ownership.

The dev server runs at http://localhost:8081.


After any docker compose down

Always run make setup — not just make start — to ensure permissions are correct.

docker compose restart (soft restart) preserves the image and is fine for quick restarts. Only make setup is needed after docker compose down.


Fix 500 errors after plugin install

If the site returns a 500 error after plugin installation or after recreating the container:

make fix-perms

This creates uid 1000 in the container, chowns /var/www/html to 1000:1000, and reloads Apache.


Upgrading the Grav core

The Grav core is baked into the custom Docker image via Dockerfile. The base getgrav/grav image ships 1.7 — the Dockerfile downloads the pinned stable bundle (grav-admin-v<version>.zip) from GitHub and overwrites the core files at build time. docker-compose.yml volume-mounts only ./user, so the core lives in the image layer. That means you upgrade the core by rebuilding the image, not by running gpm self-upgrade inside the container — an in-container self-upgrade is lost on the next rebuild. (The servers are the opposite: no image, so they self-upgrade in place.)

To upgrade:

  1. Bump both occurrences of the version in the Dockerfile release URL (the /download/<ver>/ path and the grav-admin-v<ver>.zip filename). Note: the GRAV_VERSION in .env* does not drive this build — it only pins fresh remote installs.
  2. Rebuild: docker compose build grav.
  3. Recreate the container. docker compose up -d won't replace an already-running container with a fixed container_name (it errors Conflict … name … already in use), so remove it first — safe because ./user is a bind mount:
    docker rm -f intotheeast_grav && docker compose up -d grav
    
  4. Verify: docker exec -w /var/www/html intotheeast_grav php bin/grav --version.
  5. Refresh plugins and clear cache: make install-plugins then docker exec -w /var/www/html intotheeast_grav php bin/grav cache.

Full rationale and the server-vs-local contrast: docs/solutions/tooling-decisions/upgrade-local-grav-core-rebuild-docker-image.md.


Required system.yaml settings (Grav 2.0)

After upgrading, verify these are set in user/config/system.yaml:

accounts:
  type: flex      # required for Admin2 API
pages:
  type: flex      # required for Admin2 pages API

Admin user API permissions

The admin user account needs api.* permissions for Admin2. In user/accounts/<username>.yaml:

access:
  admin:
    login: true
    super: true
  api:
    super: true
    access: true

Disable the old admin plugin

Both admin and admin2 route to /admin and conflict. After installing admin2, disable the old one:

In user/plugins/admin/admin.yaml:

enabled: false

JWT secret

Leave jwt_secret: '' in user/plugins/api/api.yaml. It works for local dev; production installs generate a secure secret automatically during make remote-install.


Language URL prefix

If Grav redirects to /en/... URLs, ensure user/config/system.yaml contains:

languages:
  supported: [en]
  include_default_lang: false

Without include_default_lang: false, Grav adds a language prefix to all URLs even for single-language sites.