Skip to content

Settle a project on the box

$ grim server:dev:add-project shop --host=dev1 --repo=acme/shop --domain=shop.dev.example.com

The box is up, Traefik is listening, and /home/dev/projects is empty. grim server:dev:add-project is how a project gets there. You run it once per project, from your own machine, and when it ends the team has a URL to open and a directory to attach an editor to.

The code comes from GitHub, not from your laptop. Whatever is not pushed does not arrive.

Usage

$ grim server:dev:add-project shop --host=dev1 --repo=acme/shop --domain=shop.dev.example.com
$ grim server:dev:add-project shop --host=dev1 --repo=acme/shop --domain=shop.dev.example.com --branch=develop
$ grim server:dev:add-project shop --host=dev-lan --repo=acme/shop --domain=shop.corp.example.com --tls=custom

--host, --repo and --domain are required. The host must be a server that grim server:dev:setup registered on this machine, and your key must be accepted for the dev user there. Expect the first run to take minutes: it installs dependencies and builds the dev image on the box.

Arguments and options

Add a project to a dev VPS (clone repo, configure dev deployment, start stack)

Usage

grim server:dev:add-project [--host HOST] [--repo REPO] [--branch BRANCH] [--domain DOMAIN] [--vite-host VITE-HOST] [--mailpit-host MAILPIT-HOST] [--tls TLS] [--] <project>

Arguments

Argument Description
project Project name (must match grim.json name in the cloned repo) (required)

Options

Option Description
--host=HOST Dev server host or alias
--repo=REPO GitHub repo slug (owner/name)
--branch=BRANCH Branch to check out (default: auto-detect, prefers main over master)
--domain=DOMAIN Public dev domain (e.g. dev.grim-foo.cz)
--vite-host=VITE-HOST Vite HMR subdomain (default: vite.)
--mailpit-host=MAILPIT-HOST Mailpit subdomain (default: mail.)
--tls=TLS TLS strategy: "acme" or "custom" (default: from server cert_mode)

What it actually does

Eight steps, each printed with its result. The first failure stops the run, except the DNS check, which only warns.

  1. Checks SSH as dev@<host>.
  2. Checks DNS. The domain, the Vite host and the Mailpit host are resolved from your machine and compared with the address of the box. Names that point elsewhere are listed as a warning and the run goes on.
  3. Clones the repository into /home/dev/projects/<project>. Without --branch it takes main, or master when there is no main. A private repository is cloned with the GitHub token of your own grim login, and the token is removed from the clone's remote URL afterwards. When the directory already holds a clone, it is fetched and fast-forwarded instead.
  4. Writes a deployment block into grim.json on the box: kind: vps-dev, the domain, vite_host and mailpit_host, plus tls: custom in custom mode. The change is left uncommitted in the working copy.
  5. Writes .env. APP_ENV=local, debug on, APP_URL on the domain, a fresh APP_KEY, generated database passwords, MySQL, Redis and Mailpit pointed at the project's own containers, the VITE_DEV_HMR_* values, and the theme variables read from grim.json. The file mode is 0644. An .env that already exists is kept; only missing theme variables are added to it.
  6. Runs grim install on the box, in the project directory.
  7. Runs grim up on the box. Because of the deployment block, this renders a compose file with the app, nginx, MySQL, Redis, a Vite container and Mailpit, routed through the box's Traefik.
  8. Remembers the project in your ~/.grim/servers.json as its dev environment: host, path, domains, repository, branch and the database password.

It ends with a summary: the three URLs, the SSH and VS Code commands, the database credentials, and a vite.config.ts snippet for projects whose Vite config does not read the VITE_DEV_HMR_* variables yet.

Three names per project

Every project takes three host names, and all three must resolve to the box.

Name Default Serves
--domain none, required The app
--vite-host vite.<domain> The Vite dev server and hot reload over wss
--mailpit-host mail.<domain> The Mailpit inbox that catches outgoing mail

How they get a certificate follows the box. --tls defaults to the cert_mode that setup saved for the server: acme asks Let's Encrypt for each name, custom uses the certificate the box already serves. With custom, that certificate has to cover all three names, so a wildcard is the practical choice.

Pitfalls

The DNS warning does not stop anything

DNS records do not yet point to 203.0.113.10 is a warning, and the stack starts anyway. In acme mode the certificates cannot be issued until the records exist, so the browser shows Traefik's default certificate until then. The check runs on your machine: a LAN name your laptop cannot resolve is reported even when the office DNS is fine.

A 400 "Untrusted Host" means PHP cannot read .env

PHP-FPM in the container runs as a different user than dev. With an unreadable .env Laravel falls back to production and rejects every host. This command writes the file as 0644 for that reason. If you replaced .env by hand, run chmod 644 .env in the project directory.

"Vite manifest not found" means the Vite container is not serving

The usual cause is an empty VITE_THEME_FRONTEND. The theme variables are taken from grim.json, and an .env that came with an older clone only gets the missing ones added. Check THEME_FRONTEND in .env, then read grim logs vite on the box.

A project name with a dash changes the database name

MySQL identifiers are written to .env lowercased, with anything outside a-z, 0-9 and _ turned into _. For my-shop the database and user are my_shop, while the summary and grim server:dev:info show my-shop. Trust .env.

A second run prints a password that is not in use

Running the command again keeps the existing .env, but generates a new database password for the summary and for servers.json. The one MySQL accepts is still the one in .env on the box.

On a second run over an existing clone, the remote URL is set to the form that carries your GitHub token and is not cleaned afterwards. Everyone on the box shares the dev user and can read it. After a re-run, reset it on the box with git remote set-url origin https://github.com/acme/shop.git.

After it finishes

Open the app URL, then hand the project to the team. Each developer needs a key on the box and an SSH block on their own machine.

$ grim server:dev:info shop
$ grim server:dev:keys add dev1 --key=~/keys/anna.pub --label=anna
$ grim server:dev:ssh-config dev1

See grim server:dev:info, grim server:dev:keys and grim server:dev:ssh-config. From then on work happens on the box, in /home/dev/projects/shop, with the same grim up, grim shell and grim artisan as anywhere else.

Recipes that use it