Skip to content

Forge the production image

$ grim build --push

A server does not run your working directory. It runs an image: the code, the Composer dependencies and the compiled assets frozen under one tag. grim build makes that image from the project you are standing in.

Most days grim release calls it for you. Reach for grim build on its own when you want the image without the deploy: to see whether the production build passes at all, to look inside the result, or to push a tag now and roll it out later with grim deploy.

Usage

$ grim build                         # build, next patch tag, keep it local
$ grim build --push                  # build and push to GHCR
$ grim build --tag=v2.0.0 --push     # choose the tag yourself
$ grim build --platform= --push      # build for the machine you are on

Run it anywhere inside the project, with Docker running. Pushing needs a login that may write packages: grim auth:login --packages-write.

Arguments and options

Build production Docker image

Usage

grim build [--push] [-t|--tag TAG] [--no-cache] [--platform PLATFORM] [-f|--dockerfile DOCKERFILE]

Options

Option Description
--push Push image to GHCR after build
-t, --tag=TAG Image tag (auto-increments patch if omitted)
--no-cache Build without cache
--platform=PLATFORM Target platform (e.g. linux/amd64) (default: linux/amd64)
-f, --dockerfile=DOCKERFILE Dockerfile path (default: docker/Dockerfile.production)

What it actually does

  1. Names the image. ghcr.io/<owner>/grim-<project>, see where the image goes.
  2. Picks the tag, unless you gave one. It asks GHCR for the tags of that image, takes the highest X.Y.Z and raises the patch: v1.4.1 becomes v1.4.2. With no version there yet it starts at v1.0.0. It prints Auto-resolved tag: v1.4.2.
  3. Checks docker/Dockerfile.production against the template of the grim you have installed, and warns when they differ: Outdated files detected. The build goes on with the file as it is.
  4. Chooses the theme to compile. THEME_FRONTEND and THEME_ADMIN from .env win, then build.vite in grim.json, then the first theme each slot declares. The choice is passed to Vite inside the build.
  5. Runs docker buildx build with docker/Dockerfile.production, for linux/amd64 unless told otherwise. The image gets two tags: the version and latest.
  6. Pushes both tags, with --push.
  7. Prints a summary. Image, size, duration, whether it was pushed, and the grim deploy line that comes next.

Where the image goes

The name is decided by grim.json alone, so that build and deploy agree whoever runs them.

  1. docker.image, when set, is used as it is, for example ghcr.io/acme/shop-web.
  2. Otherwise docker.owner gives ghcr.io/<owner>/grim-<project>.
  3. With neither, the image is ghcr.io/grimoiry/grim-<project>.

Who is logged in does not move an image. Set docker.owner to your own GitHub user or organisation and commit it. The same name is written into the docker-compose.yml on the server at deploy, so the server must be able to read that namespace; grim server:ghcr-login gives it a token for that.

Pitfalls

On Apple Silicon, no --push means no image

An amd64 image cannot be built natively on an arm64 machine, so grim creates a buildx builder named grim-builder once and builds there under emulation. With --push the result goes straight to GHCR. Without it the result stays in the builder's cache and never appears in docker images. Such a build proves the Dockerfile passes and nothing more. Pass --platform= to build a native image you can run locally.

The push is denied

denied: permission_denied: create_package means the namespace is not yours. That is the default grimoiry namespace for anybody outside it. Set docker.owner in grim.json and build again. A plain authentication failure ends with Push failed. Are you logged in? Run: docker login ghcr.io.

.env decides the theme in the image

The assets compiled into the image are those of the theme your local .env names, not the one the server's .env names. When .env names a theme the slot in grim.json does not declare, grim warns and builds with the .env value anyway. Check the two variables before building for production from a machine where you were trying another theme.

The emulated build is slow

A cross-platform build is given thirty minutes before grim gives up, a native one ten. --no-cache makes every build a first build, so keep it for when a cached layer is wrong.

After it finishes

The summary ends with the next command, filled in with the tag. It names production; put another environment in its place when that is where the image should go.

$ grim deploy production --tag=v1.4.2

When build, push and deploy always follow each other, grim release runs all three, with the test suite in front.

Recipes that use it