Skip to content

Raise a production server

$ grim server:setup 203.0.113.10 --alias=vps1 --email=ssl@example.com --ghcr-token=ghp_xxx

You have a fresh VPS and a project that is ready to go live. Before the first release can land, the machine needs Docker, a database, something to terminate TLS and an account to deploy as. grim server:setup does all of that in one run, once per server. Every project you put there afterwards shares what it built.

It is safe to run again. Each step checks what is already in place, so a second run repairs what is missing and leaves the rest alone.

Usage

$ grim server:setup 203.0.113.10 --alias=vps1 --email=ssl@example.com --ghcr-token=ghp_xxx
$ grim server:setup 203.0.113.10 --alias=vps1 --email=ssl@example.com --user=ubuntu
$ grim server:setup 10.0.0.30 --alias=vps2 --cert=~/certs/shop.pem --key=~/certs/shop-key.pem

The first line is a VPS you reach as root. The second is a cloud image that only lets ubuntu in. The third is a server whose certificates come from your own authority. Run it from anywhere; it needs no project. The alias is the name every later command takes as --host.

Arguments and options

Initialize a VPS server for GRIM deployments (Docker, Traefik, deploy user)

Usage

grim server:setup [--alias ALIAS] [--user USER] [--email EMAIL] [--cert CERT] [--key KEY] [--ghcr-token GHCR-TOKEN] [--] <host>

Arguments

Argument Description
host Server IP address or hostname (required)

Options

Option Description
--alias=ALIAS Short alias for the server (e.g., vps1)
--user=USER SSH user for initial connection (default: root)
--email=EMAIL Email for Let's Encrypt certificates (not needed with --cert/--key)
--cert=CERT Bring-your-own TLS certificate (PEM: leaf + intermediates) — turns Let's Encrypt off for this server
--key=KEY Private key (PEM) belonging to --cert
--ghcr-token=GHCR-TOKEN Classic PAT with read:packages for GHCR login (if different from auth token)

What it actually does

Ten steps, each printed with its result. The first one that fails stops the run.

  1. Checks SSH. Connects as --user, which is root unless you say otherwise.
  2. Detects the OS, for the summary at the end.
  3. Installs Docker from get.docker.com, when it is not there yet.
  4. Installs MySQL, nginx and certbot with apt, and starts them. With --cert certbot is left out.
  5. Opens MySQL to the containers. Sets bind-address to 0.0.0.0 and restarts MySQL, so that a container can reach the database on the host.
  6. Creates the deploy user. In the docker group, with /opt writable for it and your public key in its authorized_keys under the label operator.
  7. Creates the traefik-public network.
  8. Starts Traefik from /opt/traefik/docker-compose.yml, on ports 8080 and 8443. A Traefik that is already running is left as it is.
  9. Logs the deploy user in to GHCR, when --ghcr-token is given.
  10. Remembers the server in your ~/.grim/servers.json: alias, the deploy user, the login it bootstrapped with, the Let's Encrypt email and the TLS mode.

How a request travels afterwards

nginx on the host owns ports 80 and 443 and holds the certificates. Each project gets one vhost there, which passes everything to Traefik on 127.0.0.1:8080. Traefik looks at the Host header and hands the request to the right project's containers. MySQL runs on the host, one server for all projects, and the containers reach it as host.docker.internal.

That is the opposite of a shared dev box, where Traefik takes the public ports itself and every project brings its own database. The two are compared side by side in grim server:dev:setup. Do not run both on one machine.

MySQL listening on 0.0.0.0 after setup is deliberate, not a leak. The database users grim server:add-project creates may only connect from localhost and from the Docker address range 172.%. Setup configures no firewall, so keep port 3306 closed to the outside at your provider.

Two ways to get certificates

Mode Pass What changes
Let's Encrypt --email certbot is installed. Each project's certificate is issued when the project is added.
Your own certificate --cert and --key together certbot is not installed at all. The pair is checked on your machine and its paths are remembered for grim server:add-project.

With neither, setup asks for an email. The check of your own pair happens before the server is touched: PEM format, a key without a passphrase that belongs to the certificate, and an expiry date in the future. A file with the leaf certificate only passes with a warning.

Pitfalls

Cloud images do not let root in

Cannot connect to root@203.0.113.10 on a fresh AWS, GCP or Azure machine is not a firewall. Those images log in as ubuntu, ec2-user or admin. Pass that login as --user. It must have passwordless sudo, because every privileged step runs through sudo -n.

The GHCR step is green whatever happened

Step 9 shows a tick when the login worked, when it failed and when there was no token at all. You find out at the first release, when the server is refused the image. Run grim server:ghcr-login afterwards; it prints the real answer from Docker.

Your key is not where setup looks

Setup installs ~/.ssh/id_ed25519.pub, or ~/.ssh/id_rsa.pub when the first does not exist. With a key under any other name the deploy user is created without one, the step still passes, and the first deploy cannot log in. Add the key yourself with grim server:keys.

A second run does not update Traefik

Setup skips Traefik as soon as it finds it running. Template fixes that came with a newer grim reach an existing server through grim server:upgrade-traefik.

After it finishes

The summary ends with the next command, filled in with your alias. Give the server its first project, then release into it.

$ grim server:add-project shop --host=vps1 --domain=shop.example.com
$ grim release

What the second step expects is in grim server:add-project. The whole path from an empty VPS to a live site is the recipe First deploy to a fresh VPS.

Recipes that use it