Skip to content

Liber Quartus · Tome IV

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.

≈10 min grim 1.0.0
Source

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.lock

A 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.