What grim owns in your project
A project directory looks like one Laravel application. It is four kinds of files with four different owners, and every surprise an update can cause comes from mistaking one kind for another.
Open a project and you see app/, config/, src/, docker/, a .env. Nothing on disk says which of these you may edit and keep. The answer is not per directory but per owner, and there are four: the project itself, grim-core, the modules and themes, and the grim on your machine. Each owner's files arrive by a different route, and each route has its own rule for what happens to your edits.
Four owners
| Owner | Files | How they arrive | Your edits |
|---|---|---|---|
| The project | grim.json, grim.lock, pnpm-lock.yaml, .gitignore outside grim's block, .env, storage/, local modules under src/ |
Written once, then yours | Kept, always |
| grim-core | The application skeleton: app/, bootstrap/, config/, routes/, the core modules under src/, core.json |
Copied from the grim-core release | Merged with the next train |
| Modules and themes | The units grim.lock names under src/ and resources/themes/ |
Each a plain copy of its release | Merged with the next release |
| The CLI | docker/, docker-compose.yml, .dockerignore, config/modules-available.php |
Rendered from templates in grim | Depends on the file, see below |
The first row is short on purpose. What makes this project different from every other one is its manifest, its lock, its own modules, its secrets and its data. Core and the units are copies of releases anybody can fetch again.
Core and units are copies, your edits are merged
grim update lands a new core, module or theme as a diff in the working tree. The release grim.lock names is the base: a file you have not edited takes the new version, and a file you edited is merged three ways with git merge-file, so your edit survives the train or ends as a conflict for you to resolve. Update needs a clean tree and commits nothing, so the result is a diff you review and commit. It never touches grim.json, grim.lock beyond the new versions, .env or anything in storage/. The JavaScript lock is resolved in the project from core's package.json and those of its modules and themes, so it is the project's to commit.
No unit directory has a .git of its own. That is what makes a module or a core file editable in place: commit the edit in the project, then send it where it is released from with grim push — grim push Cart for a unit, grim push --core for core. The recipe Change a module and send it back walks through it.
Managed and owned infrastructure
The Docker files do not come from core. They are rendered from templates inside the CLI, and grim upgrade renews them after a grim self-update.
| Files | grim upgrade |
|
|---|---|---|
| Managed | docker/nginx-production.conf, docker/php-fpm-production.conf, docker/php-production.ini (the three rendered from performance, see grim tune), docker/nginx-dev.conf, docker/Dockerfile.production, docker/entrypoint-dev.sh, docker/php-dev.ini, docker/xdebug.ini |
Overwritten every time, no backup |
| Owned | docker-compose.yml, docker/Dockerfile.dev, docker/.env.docker.example, .dockerignore |
Left alone. With --force, backed up to .bak and regenerated |
The split follows one question: does the project have a reason to differ? A production Dockerfile that differs between projects is a bug waiting for a deploy, so grim keeps it. A compose file is where you add a service of your own, so it becomes yours the moment it is written.
config/modules-available.php is generated too, by grim install and grim update, from the modules the lock names and the local ones grim.json lists under local; grim make:module --no-repo writes it as well. Its companion storage/config/modules-enabled.php is created once as a copy and then left alone, because switching a module off there is your decision.
The repository holds the whole tree
The project repository tracks everything grim writes, core and units included, and the project's own code next to them. What stays out is a denylist, and grim owns only its block of .gitignore.
# >>> grim
/vendor/
node_modules/
/.env
/.env.*
!/.env.example
/storage/
/bootstrap/cache/*
!/bootstrap/cache/.gitkeep
/public/build/
/public/hot
/public/lang/
/public/storage
/public/svg/
/.grim/
/docker-compose.yml
/docker/*
!/docker/extra/
…
# <<< grimgit clone followed by grim install is enough to run a project: install adds vendor/, node_modules/, a .env and the Docker files. install and update rewrite the block between the two markers and never touch a line outside it, so an entry of your own goes above or below.
Code of your own needs no entry in .gitignore. A module only this project needs is a local module, grim make:module <Name> --no-repo: the one entry it needs is its name under local in grim.json, which the command writes, and it is committed like everything else.
The trace of an update is a diff in the working tree: new versions in grim.lock and the changed files of core and the units. Review it with git diff and commit it as one change.
The version of a project is an image tag
version in grim.json is a label. What identifies a release of the project is the tag of its Docker image, v1.4.2, which grim build raises by one patch each time. The image freezes all four kinds of files together: core, modules, your code and the production Dockerfile.
A server never sees the project directory. grim deploy uploads a docker-compose.yml rendered from grim.json, pulls the tag and restarts the stack. What stays on the server between deploys is what belongs to that environment alone: its .env, its storage/ volume, its database, and .deploy-history, one line per tag that went live. Staging and production differ in those and in nothing else, which is why the same tag can move from one to the other unchanged, and why Roll back a deploy needs no rebuild.
Questions
Where do I put a change to a core file?
Prefer the ways around it first: an event or a hook in a local module, a module setting, a config value set from .env, a view overridden in your theme. When the core file itself has to change, change it here and commit, then check what would travel with grim push --core --dry-run and send it with grim push --core -m "fix(admin): …". Every change inside the core boundary since the installed release goes in one pull request to the monorepo, on a branch named after the project. Once it is merged and released, grim update brings it back and the merge is clean. A rejected pull request means reverting the edit here.
Can I track docker-compose.yml in git?
You can, though grim's block of .gitignore keeps it out: the Docker files are rendered per machine and per server. Directives of your own belong in docker/extra/, which is tracked, see grim tune. A compose file you edited by hand is tracked with git add -f docker-compose.yml docker/Dockerfile.dev; git keeps following a tracked file whatever the ignore rules say. grim upgrade leaves owned files alone. grim install renders all the Docker files again when either of those two is missing, which is the case on a fresh clone that tracks only the compose file. git checkout docker-compose.yml then brings yours back.
Why is .env not in the repository?
It holds the APP_KEY and the database password of one environment. Each environment has its own: grim install writes the local one, grim server:add-project writes the server's, and grim env:pull fetches a copy when you need to read it.
Spells that do it
grim update
Board the newest train
Move core, every module and every theme onto the newest release train grim.json allows, or a single module within the train it is on.
grim upgrade
Renew the infrastructure files
Rewrite the Docker and nginx files of a project from the templates of the grim you have installed.
grim install
Assemble the whole project
Turn grim.json into a working project by fetching core, every module and theme, writing grim.lock, building the image and starting the stack.
grim deploy
Send an image to a server
Roll a pushed image tag out to one environment over SSH, verify the app answers, and fall back to the previous tag when it does not.
Put it to use
Move a project to a new train
Read the train plan, decide what core stopped shipping, update, renew the infrastructure files, and commit a lock you have tested.
Roll back a deploy
Read the deploy history, put the server back on the previous tag, check the site, and deploy an older tag by name when one step back is not enough.