Skip to content

Ship in one pass

$ grim release --force

The change is committed, it works on your machine, and it should be live. That is four commands in a fixed order, with one tag that has to be typed the same way in two of them. grim release is that order, run for you: the test suite, the build, the push and the deploy, under one tag it works out itself.

It is the everyday way to ship. Take the steps apart only when they are separate decisions, for example when the image that passed on staging should go to production unchanged.

Usage

$ grim release                       # test, build, push, deploy to production
$ grim release staging --force       # to staging, without the deploy question
$ grim release --tag=v2.0.0          # choose the tag, for a minor or major step

Run it from the project root, with the local stack up, because the tests run in the local app container. Everything grim build --push and grim deploy need applies here too: Docker, a login that may write packages, a registered server, a domain.

Arguments and options

Build, push, and deploy in one step

Usage

grim release [-t|--tag TAG] [--no-cache] [--force] [--skip-tests] [--] [<environment>]

Arguments

Argument Description
environment Target environment (production / staging / …) (optional, default: production)

Options

Option Description
-t, --tag=TAG Image tag (auto-increments patch if omitted)
--no-cache Build without cache
--force Skip deploy confirmation
--skip-tests Skip test suite before deploy

What it actually does

  1. Picks the tag, unless you gave one: the highest version tag of the image in GHCR with the patch raised, or v1.0.0 for the first release. It prints Releasing shop@v1.4.2.
  2. Runs the test suite, the same way grim test does, against the project's test database. A failure ends the release before anything is built: Tests failed — release aborted. Use --skip-tests to bypass.
  3. Builds and pushes the image under that tag, as grim build --push --tag=<tag> would. A failed build ends with Build failed — deploy skipped.
  4. Deploys the tag to the environment, as grim deploy <environment> --tag=<tag> would: config sync, pull, restart, health wait, hooks, cleanup. It asks before it starts unless --force.
  5. Reports the release to the project registry, after a successful deploy. A failure here is printed and changes nothing: Deploy OK — heartbeat is non-critical.

Pitfalls

The question comes after the build

Without --force the deploy confirmation appears when the image is already built and pushed, several minutes after you pressed enter. Walk away and the release sits there waiting. Answer no and the image stays in GHCR under its tag, undeployed; grim deploy --tag=<tag> finishes the job later.

A failed deploy still spends the tag

When the build succeeds and the deploy fails, the tag exists in GHCR. The next grim release takes the next patch number. That is harmless: tags are cheap, and the failed one can still be deployed by name once the cause is fixed.

Release cannot build for another platform

grim release passes only the tag and --no-cache on to the build. The image is always linux/amd64 from docker/Dockerfile.production. For an arm64 server or another Dockerfile, run grim build with its own options, then grim deploy.

--skip-tests removes the only check between your working directory and production. The image is built from the files on your disk, committed or not. Use it for a hotfix you have tested by other means, not to get past a red suite.

After it finishes

The deploy summary shows the tag, the URL and the state of the server. Open the URL. When the release is wrong, the previous one is one command away, and it needs no tag.

$ grim deploy production --rollback

Questions

What does the report to the registry contain?

The project name, the environment, the names of the installed modules and active themes, the core and PHP versions, the grim version, the deploy host, the app URL and how long the release took. It goes to the registry_url in grim.json, or to the grim portal when none is set. Without a grim.lock or a stored GitHub token it is skipped, and the release says so.

Which tag is running right now?

The last line of .deploy-history in the project's directory on the server. Every successful deploy and every rollback appends one.

Recipes that use it