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, .gitignore, .env, storage/ |
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 | Overwritten by the next train |
| Modules and themes | The rest of src/, resources/themes/ |
Each a git checkout at a tag | Stop the checkout until you deal with them |
| 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 secrets and its data. Everything else can be rebuilt from those.
Core is copied, modules are checked out
grim update lands a new core by copying the release over the project. It never overwrites grim.json, grim.lock, .env, .gitignore or anything in storage/. Every other core file is replaced, so an edit to config/app.php lasts until the next train.
A copy cannot delete, so update does that separately. It compares the core release the lock names with the new one and removes what core stopped shipping. A file you had edited is kept and named in a warning. A whole module that left core is never deleted; you are told to require it or remove it.
Modules and themes are different. Each directory is a git checkout of its own repository, moved to a new tag by update. Git protects your work there the usual way: a checkout that would overwrite untracked files stops, lists them and asks. That is also what makes a module editable in place, which the recipe Change a module and send it back relies on.
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/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. 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 a prescription
The .gitignore of a project is an allowlist. It ignores everything and then admits three files.
/*
/.*
!/.gitignore
!/grim.json
!/grim.lockA project repository is therefore not a copy of the application. It is the prescription for one. git clone followed by grim install rebuilds core, modules, themes and the Docker files from the lock, at exactly the versions it records. grim creates this .gitignore when it is missing and never rewrites it.
Code of your own goes on the allowlist, and from then on git tracks it like anywhere else. For a module of your own under src/, admit the directory, ignore its contents again, and admit the one module.
!/src/
/src/*
!/src/Shop/An empty git status after grim update is not a sign that nothing happened. Core and the modules are ignored by the project repository. The trace of an update is the diff of grim.lock.
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?
Not in the file. Laravel gives you the usual ways around it: a service provider in a module of your own, a config value set from .env, a published view in your theme. A change every project should have belongs upstream in grim-core, and arrives with a train.
Can I track docker-compose.yml in git?
Yes, and you should once you have edited it. Add !/docker-compose.yml to .gitignore, and docker/Dockerfile.dev with it. 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.