Skip to content
Shortspan

Search documentation

Find a page by title, section, or summary.

Deploying with a runbook

Set up a server so a runbook can deploy to it as an unprivileged account - the account, the sudo rule, the deploy key, and a check that proves it all works before the first run.

What you are setting up

By the end of this page a runbook will pull, migrate, and build a Laravel application on your server as an unprivileged account, with nobody holding the server credential and every run recorded. Most of it is one-time server setup that any least-privilege deploy needs; Shortspan checks each piece for you before the first run.

login user

How Shortspan connects - the SSH username you registered the server with. It can stay whatever it is today, including root.

run-as account

A new, ordinary account the steps run as - deploy on this page. It owns the application and nothing else.

allowed executables

The programs steps may start, as absolute paths. The server enforces them through a sudoers rule.

1. Create the run-as account

On the server, as an administrator. --disabled-password means nobody can log in as it directly; only sudo reaches it.

bash
sudo adduser --disabled-password --gecos "" deploy   # RHEL/Alma: sudo useradd -m deploy

Do not add it to sudo, wheel, or docker - each of those makes it root-equivalent, and the check fails on the first two for that reason.

2. Choose what it may run

You have two shapes to choose from. Both work; the script is narrower and simpler to write steps for.

One scriptThe tools themselves

Allowed executables

/usr/local/bin/deploy.sh

/usr/bin/git, /usr/bin/php, /usr/bin/npm

Runbook steps

One: /usr/local/bin/deploy.sh

One per tool, each with an absolute path

cd, variables, &&

Fine - it is a shell script

Not available; use -C / --prefix flags

What the account may do

Exactly what the script does

Anything git, php, or npm can do - each can run arbitrary code

/usr/local/bin/deploy.sh
#!/bin/bash
set -euo pipefail
cd /var/www/laravel
git pull
php artisan migrate --force
NODE_OPTIONS="--max-old-space-size=4192" npm run build:ssr
Owned by root and not writable by deploy, so the account cannot rewrite what it is allowed to run: `sudo chown root:root` and `sudo chmod 0755`.

Find the real paths on your server with command -v git php npm - nothing is resolved against PATH, so /usr/bin/php and /usr/bin/php8.3 are different entries.

In the console, open Infrastructure → your server → Security, turn on Restrict what may run here, and enter deploy as Run as and your choice as Allowed executables.

3. Let the login user reach it

Every step runs as sudo -n -u deploy -- <step>. The sudoers rule is the part of this the server enforces, so it names exactly the allowed executables. Replace ubuntu with your login user:

bash
echo 'ubuntu ALL=(deploy) NOPASSWD: /usr/local/bin/deploy.sh' | sudo tee /etc/sudoers.d/shortspan-deploy
sudo chmod 0440 /etc/sudoers.d/shortspan-deploy
sudo visudo -cf /etc/sudoers.d/shortspan-deploy   # must say "parsed OK"

4. Give it the application

git refuses to work in a repository another account owns ("detected dubious ownership"), and a build needs to write. Change the owner only - chown deploy without a group leaves the group, and so your web server's access, as it is:

bash
sudo chown -R deploy /var/www/laravel
sudo chmod -R u+rwX,g+rwX /var/www/laravel
sudo find /var/www/laravel -type d -exec chmod g+s {} +

The last line keeps new files in the directory's group. If you still run git or artisan there as root, git will now refuse *root* for the same reason; add the one exception with git config --global --add safe.directory /var/www/laravel, or run those commands as deploy instead (sudo -u deploy …).

5. Give it access to the repository

SSH keys belong to the account that holds them. The pull now runs as deploy, which cannot use root's key - and should not. Give it its own, read-only one:

  1. 1

    Trust the git host

    Without this, the pull fails with "Host key verification failed". Compare the printed fingerprint with the one your host publishes.

    bash
    sudo -u deploy mkdir -p -m 700 /home/deploy/.ssh
    ssh-keyscan -t ed25519 github.com | sudo -u deploy tee -a /home/deploy/.ssh/known_hosts
    ssh-keygen -lf /home/deploy/.ssh/known_hosts
  2. 2

    Create a key for deploy

    Without this, the pull fails with "Permission denied (publickey)".

    bash
    sudo -u deploy ssh-keygen -t ed25519 -N '' -f /home/deploy/.ssh/id_ed25519
    sudo cat /home/deploy/.ssh/id_ed25519.pub
  3. 3

    Add it as a read-only deploy key

    On GitHub: the repository → Settings → Deploy keys → Add deploy key, with "Allow write access" left unticked. A pull needs nothing more, and a leaked read-only key cannot push.

6. Write the runbook, then check it

Create the runbook in the console with the SSH target selected. With the script, it is one step:

text
/usr/local/bin/deploy.sh

With the tools, each step names its directory instead of relying on cd - each step runs on its own:

text
/usr/bin/git -C /var/www/laravel pull
/usr/bin/php /var/www/laravel/artisan migrate --force
/usr/bin/npm --prefix /var/www/laravel run build:ssr

Then prove it will work, without running it:

bash
shortspan runbook check academy-deploy

Beyond the server checks, this looks at each step: that the sudo rule allows that exact command, that deploy can read and write the paths it names, that git trusts the checkout, that git ls-remote succeeds with deploy's key (which fetches nothing), that the application boots with artisan --version, and that package.json defines the script. Each failure prints the fix for your server. When it says ready:

bash
shortspan runbook run academy-deploy

The execution record in the console shows each step's exit code, duration, and output, redacted before it was stored. If a run fails, the CLI points you back at runbook check.

Errors, and what they mean

You seeBecauseFix

The selected run as user is invalid / may not be root

Run as was set to root

Create an unprivileged account (step 1) and use its name

Command 'cd' is not allowed

A step is a shell line

Use git -C, --prefix, or a script (step 6)

sudo: unknown user deploy

The account does not exist on the server

Step 1

sudo: a password is required

No sudoers rule, or no NOPASSWD

Step 3

detected dubious ownership

The checkout belongs to another account

Step 4

Host key verification failed

deploy has no known_hosts entry for the git host

Step 5, first part

Permission denied (publickey)

deploy has no key the git host accepts

Step 5

execution plan integrity check failed

The plan changed between the platform and the CLI

Update the platform; report it if it persists

Something here wrong or missing? Tell us.