|
| 1 | +# shipnode v3 Reference |
| 2 | + |
| 3 | +For the 2.x single-app API (npm `latest` / `@devalade/shipnode@2`), see [REFERENCE-v2.md](REFERENCE-v2.md). |
| 4 | + |
| 5 | +## All commands |
| 6 | + |
| 7 | +Most commands accept `--config <path>` and `--app <name>` (multi-app). `rollback` requires `--app` when more than one app exists. |
| 8 | + |
| 9 | +### Core |
| 10 | +| Command | Description | |
| 11 | +|---|---| |
| 12 | +| `init [--non-interactive] [--print]` | Generate shipnode.config.ts | |
| 13 | +| `setup [--no-deploy-user]` | Install Node, PM2, Caddy, mise; bootstrap `deploy` user | |
| 14 | +| `deploy [--dry-run] [--skip-build] [--app name]` | Deploy all apps or one | |
| 15 | +| `doctor [--security]` | Local + remote config / security audit | |
| 16 | +| `status [--app name]` | PM2 process status | |
| 17 | + |
| 18 | +### Release management |
| 19 | +| Command | Description | |
| 20 | +|---|---| |
| 21 | +| `rollback --app name [--steps n]` | Roll back n releases (default 1). Blue-green: one-step Caddy flip | |
| 22 | +| `migrate` | Migrate existing deploy to release/symlink structure | |
| 23 | + |
| 24 | +### Environment & process |
| 25 | +| Command | Description | |
| 26 | +|---|---| |
| 27 | +| `env [--file path] [--app name]` | Upload .env → `<appPath>/shared/` | |
| 28 | +| `run [cmd] [--tty] [--app name]` | One-off remote command (or alias) | |
| 29 | +| `logs [--lines n] [--app name]` | Tail PM2 logs | |
| 30 | +| `restart [--app name]` | Reload PM2 with `--update-env` | |
| 31 | +| `stop [--app name]` | Stop the application | |
| 32 | +| `metrics` | PM2 monitoring dashboard | |
| 33 | +| `monitor [--app name]` | Live TUI (PM2, system, health, logs) | |
| 34 | +| `unlock` | Clear stuck deploy lock (`.shipnode/deploy.lock/`) | |
| 35 | + |
| 36 | +### Accessories & secrets |
| 37 | +| Command | Description | |
| 38 | +|---|---| |
| 39 | +| `accessory status [name]` | Docker accessory status | |
| 40 | +| `accessory logs <name>` | Accessory logs | |
| 41 | +| `accessory restart <name>` | Restart accessory | |
| 42 | +| `accessory stop <name>` | Stop accessory | |
| 43 | +| `accessory health <name>` | Run accessory health check | |
| 44 | +| `secret set <name> [value]` | Set remote secret (value or local env) | |
| 45 | +| `registry login` | Store registry token + `docker login` on target | |
| 46 | + |
| 47 | +### Security & maintenance |
| 48 | +| Command | Description | |
| 49 | +|---|---| |
| 50 | +| `harden` | SSH hardening, UFW, fail2ban, PM2 boot check | |
| 51 | +| `doctor --security` | Security audit | |
| 52 | + |
| 53 | +### Users |
| 54 | +| Command | Description | |
| 55 | +|---|---| |
| 56 | +| `user add <name> [--key path] [--sudo] [--no-sync]` | Write `.shipnode/users.yml` + sync | |
| 57 | +| `user sync` | Sync users from `.shipnode/users.yml` | |
| 58 | +| `user list` | List non-system users | |
| 59 | +| `user remove <username>` | Remove user | |
| 60 | + |
| 61 | +### Backup |
| 62 | +| Command | Description | |
| 63 | +|---|---| |
| 64 | +| `backup setup` | Install script + systemd timer | |
| 65 | +| `backup run` | Run immediately | |
| 66 | +| `backup status` | Timer + last run | |
| 67 | +| `backup list` | List S3 / restic snapshots | |
| 68 | +| `backup restore [snapshot] --target <path>` | Extract restic snapshot (default `latest`) | |
| 69 | + |
| 70 | +### Cloudflare |
| 71 | +| Command | Description | |
| 72 | +|---|---| |
| 73 | +| `cloudflare init` | Tunnel + DNS + ingress for every app domain | |
| 74 | +| `cloudflare audit` | Verify DNS + tunnel | |
| 75 | +| `cloudflare status` | cloudflared service status | |
| 76 | + |
| 77 | +### CI/CD |
| 78 | +| Command | Description | |
| 79 | +|---|---| |
| 80 | +| `ci github` | Write `.github/workflows/shipnode-deploy.yml` | |
| 81 | +| `ci env-sync [--all]` | Push .env → GitHub repo secrets | |
| 82 | + |
| 83 | +### Config |
| 84 | +| Command | Description | |
| 85 | +|---|---| |
| 86 | +| `config show [--app name]` | Print resolved config | |
| 87 | +| `config validate` | Validate config file | |
| 88 | +| `config path` | Print config file path | |
| 89 | + |
| 90 | +### Misc |
| 91 | +| Command | Description | |
| 92 | +|---|---| |
| 93 | +| `eject [pm2\|caddy\|all]` | Eject templates to `.shipnode/templates/` | |
| 94 | +| `upgrade` | Upgrade shipnode CLI | |
| 95 | + |
| 96 | +--- |
| 97 | + |
| 98 | +## Workspace builder (root) |
| 99 | + |
| 100 | +| Method | Default | Description | |
| 101 | +|---|---|---| |
| 102 | +| `.ssh({ host, user, port?, identityFile? })` | port 22 | Default SSH connection | |
| 103 | +| `.servers({ name: SshConfig })` | — | Named server targets | |
| 104 | +| `.cloudflareAccess(proxyCommand?)` | — | SSH via Cloudflare Access | |
| 105 | +| `.deployTo(path)` | `/var/www/app` | Workspace remote base path | |
| 106 | +| `.nodeVersion(v)` | `lts` | Node via mise | |
| 107 | +| `.pkgManager(pm, { installCommand? })` | auto | `npm` \| `yarn` \| `pnpm` \| `bun` | |
| 108 | +| `.installCommand(cmd)` | pkg default | Override remote install | |
| 109 | +| `.database(opts)` | — | Postgres/MySQL/SQLite provisioning hints | |
| 110 | +| `.redis(opts)` | — | Redis connection | |
| 111 | +| `.backup(opts)` | — | S3 / restic backup config | |
| 112 | +| `.cloudflare(opts)` | — | Tunnel zone / name / lockdown | |
| 113 | +| `.aliases(map)` | — | Named `shipnode run` shortcuts | |
| 114 | +| `.registry({ server, username, passwordEnv })` | — | Default Docker registry | |
| 115 | +| `.accessories(map)` | — | Docker sidecar definitions | |
| 116 | +| `.app()` | — | Start a per-app sub-builder | |
| 117 | +| `.apps([appBuilders])` | — | Compose multi-app workspace (wins over top-level per-app methods) | |
| 118 | +| `.backend()` / `.frontend()` … | — | Legacy single-app shortcuts → `apps[0]` | |
| 119 | +| `.build()` | — | Assemble + validate → `ShipnodeConfig` | |
| 120 | + |
| 121 | +### Backup options |
| 122 | +```ts |
| 123 | +.backup({ |
| 124 | + s3Bucket: string, |
| 125 | + s3Prefix?: string, |
| 126 | + s3Endpoint?: string, // e.g. R2 |
| 127 | + schedule?: 'hourly' | 'daily' | 'weekly', |
| 128 | + retentionDays?: number, // snapshot strategy |
| 129 | + strategy?: 'snapshot' | 'restic', |
| 130 | + keepDaily?: number, // restic (default 7) |
| 131 | + keepWeekly?: number, // default 4 |
| 132 | + keepMonthly?: number, // default 6 |
| 133 | +}) |
| 134 | +``` |
| 135 | + |
| 136 | +### Accessory shape |
| 137 | +```ts |
| 138 | +{ |
| 139 | + image: string, |
| 140 | + on?: string, // server target name |
| 141 | + port?: string | string[], |
| 142 | + volumes?: string[], |
| 143 | + networks?: string[], |
| 144 | + command?: string | string[], |
| 145 | + env?: Record<string, string>, |
| 146 | + restart?: 'no' | 'always' | 'unless-stopped' | 'on-failure', |
| 147 | + registry?: { server, username, passwordEnv }, |
| 148 | + healthCheck?: { command: string }, |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +## Per-app builder (`app()` / `shipnode.app()`) |
| 155 | + |
| 156 | +| Method | Default | Description | |
| 157 | +|---|---|---| |
| 158 | +| `.backend()` / `.frontend()` | — | App type (required) | |
| 159 | +| `.name(n)` | inferred | App id → release dir + CLI `--app` | |
| 160 | +| `.on(serverName)` | default ssh | Target from `.servers()` | |
| 161 | +| `.appRoot(dir)` | `.` | Monorepo package path | |
| 162 | +| `.pm2(name, { instances?, maxMemory? })` | — | Primary PM2 process | |
| 163 | +| `.port(n)` | — | Web port (marks the HTTP app; sets `PORT`) | |
| 164 | +| `.worker({ name, command, … })` | — | Extra PM2 process (no port) | |
| 165 | +| `.domain(d)` | — | Caddy + Cloudflare ingress | |
| 166 | +| `.caddy({ append })` | — | Extra Caddy directives | |
| 167 | +| `.buildDir(dir)` | auto | Frontend output dir | |
| 168 | +| `.envFile(f)` | `.env` | Uploaded to `shared/`, symlinked as `.env` | |
| 169 | +| `.keepReleases(n)` | `5` | Releases to retain | |
| 170 | +| `.zeroDowntime(altPort?)` | automatic when eligible | Force blue-green or choose the alternate port; default offset is 10,000 | |
| 171 | +| `.noZeroDowntime()` | — | Opt out of automatic blue-green | |
| 172 | +| `.sharedDirs(dirs)` / `.sharedFiles(files)` | — | Persist across releases | |
| 173 | +| `.healthCheck(path, opts?)` | `/health`, 30s, 3 retries, 3s delay | Post-deploy probe | |
| 174 | +| `.noHealthCheck()` | — | Skip health check | |
| 175 | +| `.preDeploy(fn)` / `.postDeploy(fn)` | — | Deploy hooks | |
| 176 | +| `.dependsOn(['accessory'])` | — | Ensure accessories before deploy | |
| 177 | + |
| 178 | +--- |
| 179 | + |
| 180 | +## Deploy lifecycle |
| 181 | + |
| 182 | +``` |
| 183 | +lock → accessories → per app: |
| 184 | + release dir → stage → setup (install/build) → preDeploy |
| 185 | + → symlink current → start (web colour if blue-green) |
| 186 | + → health → afterHealthy (workers) → Caddy flip (blue-green) |
| 187 | + → record success → postDeploy → cleanup |
| 188 | +→ caddy.configureAll → unlock |
| 189 | +``` |
| 190 | + |
| 191 | +On failure after symlink: revert `current` (if previous exists), record `failed`, unlock. |
| 192 | + |
| 193 | +### Blue-green notes |
| 194 | +- Requires backend + one PM2 app with `.port()`. |
| 195 | +- Web process duplicated (`name-blue` / `name-green`); workers single set, reloaded only after health. |
| 196 | +- State: `<appPath>/.shipnode/deploy-state.json` (port pair frozen after first deploy). |
| 197 | +- Health failure: old colour keeps traffic; workers untouched; `current` reverted. |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +## Deploy hook API |
| 202 | + |
| 203 | +```ts |
| 204 | +.preDeploy(async ({ exec, config }) => { ... }) |
| 205 | +.postDeploy(async ({ exec, config }) => { ... }) |
| 206 | +``` |
| 207 | + |
| 208 | +`exec(cmd)` runs on the remote in the new release dir (or `<release>/<appRoot>`). Env file sourced when configured. Throws on non-zero exit. |
| 209 | + |
| 210 | +--- |
| 211 | + |
| 212 | +## Run aliases |
| 213 | + |
| 214 | +```ts |
| 215 | +.aliases({ |
| 216 | + migrate: 'pnpm db:apply', |
| 217 | + seed: 'pnpm db:seed', |
| 218 | +}) |
| 219 | +``` |
| 220 | + |
| 221 | +```bash |
| 222 | +shipnode run migrate |
| 223 | +shipnode run migrate --step 2 |
| 224 | +shipnode run "echo hello" |
| 225 | +``` |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +## Paths (per app) |
| 230 | + |
| 231 | +| Path | Purpose | |
| 232 | +|---|---| |
| 233 | +| `<remotePath>/<app>/releases/<ts>/` | Release snapshot | |
| 234 | +| `<remotePath>/<app>/current` | Symlink to active release | |
| 235 | +| `<remotePath>/<app>/shared/` | Env + shared dirs/files | |
| 236 | +| `<remotePath>/<app>/.shipnode/releases.json` | Release history | |
| 237 | +| `<remotePath>/<app>/.shipnode/deploy-state.json` | Blue-green colour/ports | |
| 238 | +| `<remotePath>/.shipnode/deploy.lock/` | Workspace deploy lock (mkdir) | |
0 commit comments