Skip to content

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 for

Run 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] [--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
--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.

  1. Resolves core. The core constraint in grim.json becomes a grim-core tag. In a directory without composer.json and config/ the core tree is fetched, at the version grim.lock records when there is a lock, and the step reads Installing grim-core. Where they exist it reads Resolving grim-core and core on disk is left alone.
  2. Checks package availability. Every module and theme in grim.json is looked up before anything is cloned. The same check runs again on the full closure after step 3.
  3. Resolves dependencies. The requires of grim.json, then the requires of every module.json they lead to, each to the version grim.lock records. A module the lock does not know yet gets the newest tag its constraint allows on the project's train.
  4. Installs modules. One git checkout per module under src/<Module>/.
  5. Installs themes. Every theme of every slot, into resources/themes/<type>/<name>. The first of a slot is the active one.
  6. Generates configuration. The module lists, the Docker files and .env, each under the conditions in the table below. An empty APP_KEY is filled in, and shared secrets from the registry are written into .env.
  7. Writes grim.lock. Train, core version and commit, every module with version, commit and requires, every theme.
  8. Creates the local database. Only when a MySQL on your machine answers to root without a password. The name is DB_DATABASE from .env, reduced to lowercase letters, digits and underscores.
  9. Builds and starts. Builds the image, runs composer install in a one-off container unless vendor/ already matches composer.lock, makes sure the local Traefik is up, starts the stack and waits for the app container. Then yarn install and a Vite build on your machine, when yarn is there.
  10. Migrates, generates translations, creates an admin. php artisan migrate --force, lang:js and users:admin, all in the app container.

The closing table shows project, core, the number of modules and themes and, 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. 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.

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

Questions

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.

Recipes that use it

The idea underneath