# 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: ```bash mkdir -p user/plugins user/data ``` Then run: ```bash 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: ```bash 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.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//` path and the `grav-admin-v.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: ```bash 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`: ```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/.yaml`: ```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`: ```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: ```yaml 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.