Assemble the whole project
$ grim install --dry-run
You have a directory with a grim.json in it and not much else: a project cloned from its repository, which carries grim.json and grim.lock and leaves core, modules and themes to be fetched, or one whose grim.json you edited by hand. grim install makes the directory match the file. On a fresh clone that means everything, from fetching core to a running stack with an admin account.
grim create-project ends by running it for you. To add one package to a project that is already installed, grim require is the shorter road. To move an installed project to a newer core, the command is grim update; install never does that.
Usage
$ grim install --dry-run # resolve and report, touch nothing
$ grim install # the whole thing
$ grim install --no-composer # files and lock only, no build, nothing started
$ grim install --dry-run --strict # for CI: exit 1 on modules nobody asked forRun it anywhere inside the project; it walks up to the nearest grim.json. You must be logged in with grim auth:login, and Docker must be running unless you pass --no-docker or --dry-run.
Arguments and options
Install modules and themes from grim.json
Usage
grim install [--dry-run] [--strict] [--no-composer] [--force-composer] [--no-docker] [--stop-herd] [--regenerate-docker] [--no-enable] [--allow-unpublished-theme ALLOW-UNPUBLISHED-THEME]Options
| Option | Description |
|---|---|
--dry-run |
Resolve and report what would be installed, without touching the project |
--strict |
With --dry-run: exit 1 when the closure contains modules grim.json does not require |
--no-composer |
Skip composer install |
--force-composer |
Run composer install even when vendor/ already matches composer.lock |
--no-docker |
Skip Docker file generation |
--stop-herd |
Stop Laravel Herd when its nginx holds ports 80/443 (never done without this flag) |
--regenerate-docker |
Rewrite docker-compose.yml and docker/ from the current templates, even when they exist (the old files are kept as *.bak) |
--no-enable |
Skip enabling modules (generate modules-available.php only) |
--allow-unpublished-theme=ALLOW-UNPUBLISHED-THEME |
Resolve the named theme slug from resources/themes/<type>/<slug>/theme.json without contacting GitHub. Used by create-project --new-theme when the publish pipeline could not push the new theme (missing git identity, no GitHub permissions, etc.). |
What it actually does
Each step is printed with its duration, and the first failure stops the run.
- Resolves core. The
coreconstraint ingrim.jsonbecomes a grim-core tag. In a directory withoutcomposer.jsonandconfig/the core tree is fetched, at the versiongrim.lockrecords when there is a lock, and the step readsInstalling grim-core. Where they exist it readsResolving grim-coreand core on disk is left alone. Either way the rootREADME.mdandCLAUDE.mdare the project's, not core's: they are written when missing (short, about this instance) and never overwritten once they are yours, except that aCLAUDE.mdwithout the@AGENTS.mdimport gets it as its first line (that import loads grim-core's agent map), and a README orCLAUDE.mdan earlier grim wrote and nobody edited — a README from before model A among them — is replaced by the current one. - Checks package availability. Every module and theme in
grim.jsonis looked up before anything is cloned. The same check runs again on the full closure after step 3. - Resolves dependencies. The
requiresofgrim.json, then therequiresof everymodule.jsonthey lead to, each to the versiongrim.lockrecords. A module the lock does not know yet gets the newest tag its constraint allows on the project's train. - Installs modules. One plain copy of its release per module under
src/<Module>/. A module directory already in the project, as after agit clone, is left as it is. - Installs themes. Every theme of every slot, into
resources/themes/<type>/<name>. The first of a slot is the active one. - Generates configuration. The module lists, the Docker files and
.env, each under the conditions in the table below. An emptyAPP_KEYis filled in, and shared secrets from the registry are written into.env. - Writes
grim.lock. Train, core version and commit, every module with version, commit andrequires, every theme. - Creates the local database. It logs in with
DB_USERNAME,DB_PASSWORDandDB_PORTfrom.env(host.docker.internalmeans127.0.0.1here), so a MariaDB with its own user works once those lines are right. When that login fails, or it may not create the database, the install goes on without one: files, image and dependencies still land, step 10 is skipped, and a yellowDatabase unreachable as …above the summary names the login it tried. Put the rightDB_*lines into.envand rungrim installagain; the second run finds the database and does step 10. Without amysqlclient on the machine nothing is checked, and a warning after the steps says so. ADB_HOSTthat is a compose service or another server is left alone. The name isDB_DATABASEfrom.env, reduced to lowercase letters, digits and underscores. - Builds and starts. Builds the image, runs
composer installin a one-off container unlessvendor/already matchescomposer.lock, makes sure the local Traefik is up, starts the stack and waits for theappcontainer. Thenpnpm installand a Vite build on your machine, whenpnpmis there (npm for a project that ships apackage-lock.json). When the install writes or changespnpm-lock.yamlin a project with a git repository, the summary ends withpnpm-lock.yaml written — commit it.orpnpm-lock.yaml changed — commit it. - Migrates, generates translations, creates an admin.
php artisan migrate --force,lang:jsandusers:admin, all in theappcontainer.
The closing table shows project, core, the number of modules and themes and, only when the account was created, the admin email admin@<project domain> with a generated password. With --no-composer, steps 9 and 10 do not run at all.
The admin password is printed once and cannot be shown again. Copy it before you clear the terminal. grim user makes a new account if it is lost.
Reading the dry run
--dry-run needs no Docker and writes nothing. It prints a table of every module the install would land, with its version and a why column: grim.json, or the modules that require it. Then the themes with (active) on the first of each slot, the core constraint as written, and a total.
Between the table and the themes sits the part worth reading when you take over a project.
Resolved but not in grim.json:
`payments` required by `cart`Each line is a module the project would get without having asked for it. That is normal for a dependency. It becomes a finding when the app uses the module directly: then it belongs in grim.json. With --strict a non-empty section ends the run with exit code 1 and --strict: 1 module(s) resolved that grim.json does not require.
What it generates, and when
| Files | Written |
|---|---|
grim.lock, config/modules-available.php |
On every run. |
docker-compose.yml, .dockerignore, and in docker/: Dockerfile.dev, Dockerfile.production, nginx-dev.conf, .env.docker.example |
Only when docker-compose.yml or docker/Dockerfile.dev is missing, or with --regenerate-docker. Never with --no-docker. |
docker/nginx-production.conf, docker/php-override.ini |
Overwritten on every run without --no-docker. |
.env, storage/config/modules-enabled.php |
Only when missing. .env is a copy of docker/.env.docker.example; the enabled list is a copy of the available one. |
An installed project keeps its compose file and Dockerfiles through any number of installs. Bringing those up to date is the job of grim upgrade, or of --regenerate-docker, which rewrites them from the current templates and leaves the old ones beside them as *.bak.
docker-compose.override.yml is yours. grim never writes it, and the compose commands grim runs read it after docker-compose.yml, the same as a bare docker compose would.
Install reads them on the way past: a Dockerfile that builds FROM an image in ghcr.io that is not one of grim's base images is named at the end of the run, with that flag as the way out. It is what a project carried over from another brand looks like, and the image it names has usually stopped existing.
Pitfalls
Install does not move an installed core
Core 2.4.2 is installed but 2.5.0 satisfies "~2.4" — run `grim update` to move onto it. is a notice, not an error. Once core is on disk, install records the version the lock already had and changes nothing under it. Run grim update --dry-run when you want the move.
The lock wins over a newer tag
Install reproduces, it does not move. Core, modules and themes land at the versions in grim.lock, so a fresh clone matches what your colleagues committed even when newer tags exist. Only a unit the lock has never seen is resolved fresh, and then within the train the lock names. Newer versions are the business of grim update.
A constraint in grim.json that you tightened by hand can exclude the locked version. Install then stops with No version of module 'cart' satisfies constraint '~2.5' and lists the locked version as the only one available. Run grim update, which rewrites the lock.
One unavailable package stops everything
1 package cannot be installed:
webhooks is archived — it is part of core since 2.5; remove it from grim.json
Nothing was installed. Fix grim.json (or request access) and run the command again.There is no partial install. The message names the remedy for each package: remove it from grim.json, or ask for access to a private one.
Shared secrets replace values you set yourself
A key that exists both in .env and in the registry gets the registry's value on every install. Keep project-specific values under names the registry does not use. When the registry cannot be reached the step is skipped without a word; -v prints the reason.
A missing PHP extension is only a warning
⚠ php extension "soap" is missing — required by invoices does not fail the run. The check reads php.extensions from the installed modules and compares it with php -m in the app container, or on your machine when the container is not up. Add the extension to the image before you use those modules.
After it finishes
The stack is already up; grim up adds the Vite dev server. When grim.json no longer requires modules that are still in src/, install lists them and offers to delete them, which is grim prune run for you. Then commit the lock.
$ grim up
$ git add grim.lockQuestions
Do I run install after every git pull?
When grim.json or grim.lock changed, yes: install lands the modules and themes the new lock records. It does not move a core that is already on disk. When the lock names a newer core or train, run grim update instead.
Why did install not enable my new module?
modules-enabled.php is copied from the available list only when it does not exist. On an installed project, grim require enables what it adds; after a hand edit of grim.json, add the provider line yourself.
Related spells
grim require
Summon a module or theme
Add a module or theme to grim.json, install it with everything it depends on, and rewrite grim.lock, all inside the train the project is on.
grim remove
Banish a module
Drop a module from grim.json and delete it from src/ together with every installed module nothing requires any more.
grim prune
Clear what nothing asks for
Delete the modules under src/ that can no longer be reached from the requires in grim.json.
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 up
Raise the stack
Start the project's containers and its Vite dev server, with a trusted certificate and a working address, and say where to open it.
grim create-project
Conjure a project
Create a project directory with a grim.json on the current train and install it, or write the grim.json for an application that already runs.
Recipes that use it
Onboard as a collaborator
Get access to the packages, log grim in, clone the project, install what its lock records, bring the stack up and run the suite once.
Bring an existing app onto grim
Convert the instance into a grim.json without installing, read the dry run, settle the modules nobody asked for, point the copied .env at this machine, install, move your own files in, then test and commit.