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.
- Checks SSH as
dev@<host>. - 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.
- Clones the repository into
/home/dev/projects/<project>. Without--branchit takesmain, ormasterwhen there is nomain. 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. - Writes a
deploymentblock intogrim.jsonon the box:kind: vps-dev, the domain,vite_hostandmailpit_host, plustls: customin custom mode. The change is left uncommitted in the working copy. - Writes
.env.APP_ENV=local, debug on,APP_URLon the domain, a freshAPP_KEY, generated database passwords, MySQL, Redis and Mailpit pointed at the project's own containers, theVITE_DEV_HMR_*values, and the theme variables read fromgrim.json. The file mode is0644. An.envthat already exists is kept; only missing theme variables are added to it. - Runs
grim installon the box, in the project directory. - Runs
grim upon the box. Because of thedeploymentblock, this renders a compose file with the app, nginx, MySQL, Redis, a Vite container and Mailpit, routed through the box's Traefik. - Remembers the project in your
~/.grim/servers.jsonas itsdevenvironment: 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 dev1See 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.
Related spells
grim server:dev:setup
Raise a shared dev box
Turn a bare VPS or LAN machine into a shared development server with Docker, Traefik, one dev user and grim itself.
grim server:dev:info
Show a project's dev card
Print the URLs, SSH and VS Code commands, database credentials and container states of a project on a dev box.
grim server:dev:reload
Recreate a dev stack
Recreate the containers of a project on a dev box from your own machine, so a changed .env or grim.json takes effect.
grim server:dev:remove
Take a project off the box
Stop a project's stack on a dev box and delete its volumes, its directory and its registration, keeping what you ask to keep.
Recipes that use it
Set up a dev box for a client
Raise a shared development server, give it a registry token, put the first project on it, let developers in by key, hand over the access card, and later take a developer or a project off again.
LAN dev box with a custom certificate
Check a certificate from your own authority by hand, raise a dev box that serves it, put a project on it under names one wildcard covers, make developer machines trust the authority, and renew the certificate without downtime.