|
1 | 1 | --- |
2 | 2 | title: Quick start |
3 | | -description: Three commands from empty server to deployed app. |
| 3 | +description: Everything you need to go from an empty Ubuntu VPS to a deployed Node.js app, in about ten minutes. |
4 | 4 | --- |
5 | 5 |
|
6 | | -After [installing shipnode](/docs/install/) in your project: |
| 6 | +This walkthrough covers the **full happy path** — provisioning a fresh server, generating a config, deploying your first release, rolling back, and wiring CI. Skip ahead if a section doesn't apply. |
| 7 | + |
| 8 | +:::tip[Use the ShipNode AI skill in Claude Code] |
| 9 | +Inside [Claude Code](https://claude.com/claude-code), the `shipnode` skill knows this CLI end-to-end — deploying, rolling back, configuring Caddy/PM2, managing `.env`, and reading server state. Type `/shipnode` in Claude Code or just describe what you want; the skill is auto-triggered when relevant. Every page on this site also has **Copy as Markdown / Open in Claude / Open in ChatGPT** buttons at the top so you can hand the docs to any AI assistant. |
| 10 | +::: |
| 11 | + |
| 12 | +## 0. Prerequisites |
| 13 | + |
| 14 | +| You need | Why | |
| 15 | +|---|---| |
| 16 | +| **A VPS** running Ubuntu 22.04+ or Debian 12+ | ShipNode provisions Node, PM2, Caddy on this OS. | |
| 17 | +| **A non-root SSH user** with `sudo` and a public key in `~/.ssh/authorized_keys` | All `shipnode` commands run as this user. | |
| 18 | +| **A domain** pointing an A record at the VPS IP | Caddy will issue an HTTPS cert automatically. | |
| 19 | +| **Node.js 18+ locally** | The CLI is a Node.js package. | |
| 20 | + |
| 21 | +If your VPS only has a root user right now, create the deploy user first: |
| 22 | + |
| 23 | +```bash |
| 24 | +ssh root@your.vps.ip |
| 25 | +adduser deploy |
| 26 | +usermod -aG sudo deploy |
| 27 | +mkdir -p /home/deploy/.ssh |
| 28 | +cp ~/.ssh/authorized_keys /home/deploy/.ssh/ |
| 29 | +chown -R deploy:deploy /home/deploy/.ssh |
| 30 | +chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys |
| 31 | +``` |
| 32 | + |
| 33 | +## 1. Install shipnode in your project |
| 34 | + |
| 35 | +```bash |
| 36 | +# npm |
| 37 | +npm install -D @devalade/shipnode |
| 38 | + |
| 39 | +# pnpm |
| 40 | +pnpm add -D @devalade/shipnode |
| 41 | + |
| 42 | +# yarn |
| 43 | +yarn add -D @devalade/shipnode |
| 44 | + |
| 45 | +# bun |
| 46 | +bun add -d @devalade/shipnode |
| 47 | +``` |
| 48 | + |
| 49 | +`shipnode` lives in your project as a dev dependency so every collaborator and your CI uses the same version. |
| 50 | + |
| 51 | +## 2. Generate the config |
7 | 52 |
|
8 | 53 | ```bash |
9 | | -# 1. Generate config (interactive — detects framework, package manager, app type) |
10 | 54 | npx shipnode init |
| 55 | +``` |
| 56 | + |
| 57 | +This prompts for framework, package manager, app type, SSH target, domain, and port — then writes `shipnode.config.ts`. You can rerun `init` or edit the file by hand at any time. See the [configuration reference](/docs/configuration/) for every option. |
| 58 | + |
| 59 | +A minimal backend config: |
| 60 | + |
| 61 | +```ts |
| 62 | +import { shipnode } from '@devalade/shipnode'; |
11 | 63 |
|
12 | | -# 2. Provision the server (Node.js, PM2, Caddy, package manager) |
| 64 | +export default shipnode |
| 65 | + .backend() |
| 66 | + .ssh({ host: '203.0.113.10', user: 'deploy' }) |
| 67 | + .deployTo('/var/www/api') |
| 68 | + .pm2('api', { instances: 2 }) |
| 69 | + .port(3000) |
| 70 | + .domain('api.example.com') |
| 71 | + .healthCheck('/health') |
| 72 | + .nodeVersion('22') |
| 73 | + .pkgManager('pnpm') |
| 74 | + .build(); |
| 75 | +``` |
| 76 | + |
| 77 | +## 3. Provision the server |
| 78 | + |
| 79 | +```bash |
13 | 80 | npx shipnode setup |
| 81 | +``` |
| 82 | + |
| 83 | +One-time, idempotent. Installs **mise**, **Node.js**, **PM2** (+ `pm2-logrotate`), **Caddy**, and your package manager. Re-running it is safe — it skips anything already present. |
| 84 | + |
| 85 | +Verify with: |
| 86 | + |
| 87 | +```bash |
| 88 | +npx shipnode doctor |
| 89 | +``` |
| 90 | + |
| 91 | +If anything is red, fix it before deploying. |
| 92 | + |
| 93 | +## 4. Upload secrets |
14 | 94 |
|
15 | | -# 3. Deploy |
| 95 | +If your app reads from `.env`, push it once: |
| 96 | + |
| 97 | +```bash |
| 98 | +npx shipnode env |
| 99 | +``` |
| 100 | + |
| 101 | +The file lands at `<deployPath>/shared/.env` and is symlinked into every release. PM2 picks up changes on the next `restart` or `deploy` (both use `--update-env`). |
| 102 | + |
| 103 | +## 5. Deploy |
| 104 | + |
| 105 | +```bash |
16 | 106 | npx shipnode deploy |
17 | 107 | ``` |
18 | 108 |
|
19 | | -That's the full path. `init` writes `shipnode.config.ts`. `setup` is a one-time server provision. `deploy` builds, syncs, releases, reloads PM2, and health-checks before declaring the release healthy. |
| 109 | +What happens: |
| 110 | + |
| 111 | +``` |
| 112 | +rsync ./ -> /var/www/api/releases/20260524160000 |
| 113 | +install pnpm install --frozen-lockfile |
| 114 | +build pnpm run build |
| 115 | +symlink current -> releases/20260524160000 |
| 116 | +pm2 reload api --update-env |
| 117 | +health GET /health 200 OK 47ms |
| 118 | +deployed https://api.example.com |
| 119 | +``` |
| 120 | + |
| 121 | +If the health check fails, the symlink stays on the previous release and the failed one is discarded. No partial outage. |
20 | 122 |
|
21 | | -## What the deploy actually does |
| 123 | +## 6. Confirm and operate |
22 | 124 |
|
| 125 | +```bash |
| 126 | +npx shipnode status # PM2 state + current release |
| 127 | +npx shipnode logs # stream logs |
| 128 | +npx shipnode metrics # PM2 CPU/memory dashboard |
23 | 129 | ``` |
24 | | -rsync ./ -> /var/www/api/releases/20260524160000 |
25 | | -install pnpm install --frozen-lockfile |
26 | | -build pnpm run build |
27 | | -symlink current -> releases/20260524160000 |
28 | | -pm2 reload api --update-env |
29 | | -health GET /health 200 OK 47ms |
30 | | -deployed https://api.example.com |
| 130 | + |
| 131 | +Need to run a one-off command on the server inside the current release? |
| 132 | + |
| 133 | +```bash |
| 134 | +npx shipnode run "node scripts/migrate.js" |
31 | 135 | ``` |
32 | 136 |
|
33 | | -If the health check fails, the symlink stays on the previous release and the new one is discarded. |
| 137 | +## 7. Roll back |
34 | 138 |
|
35 | | -## Roll back |
| 139 | +Something off in production? |
36 | 140 |
|
37 | 141 | ```bash |
38 | 142 | npx shipnode rollback --steps 1 |
39 | 143 | ``` |
40 | 144 |
|
41 | | -## Next |
| 145 | +The `current` symlink moves back one release and PM2 reloads. The default `keepReleases` is 5, so you have headroom. |
| 146 | + |
| 147 | +## 8. Wire CI (optional) |
| 148 | + |
| 149 | +Generate a ready-to-use GitHub Actions workflow: |
| 150 | + |
| 151 | +```bash |
| 152 | +npx shipnode ci github |
| 153 | +npx shipnode ci env-sync --all |
| 154 | +``` |
| 155 | + |
| 156 | +This drops `.github/workflows/deploy.yml` and pushes your `.env` keys to GitHub repository secrets. See the [CI/CD guide](/docs/ci-cd/) for the secrets it expects. |
| 157 | + |
| 158 | +## 9. Harden the server (recommended) |
| 159 | + |
| 160 | +```bash |
| 161 | +npx shipnode harden |
| 162 | +``` |
| 163 | + |
| 164 | +Locks down SSH (key-only, no root), enables UFW, installs `fail2ban`, and turns on unattended security upgrades. |
| 165 | + |
| 166 | +Audit it anytime: |
| 167 | + |
| 168 | +```bash |
| 169 | +npx shipnode doctor --security |
| 170 | +``` |
| 171 | + |
| 172 | +## What's next |
42 | 173 |
|
43 | | -- [Configuration](/docs/configuration/) — the full `shipnode.config.ts` reference |
44 | | -- [Multi-environment](/docs/environments/) — staging + production from one repo |
45 | | -- [CI/CD](/docs/ci-cd/) — generate a GitHub Actions workflow |
| 174 | +- [shipnode.config.ts reference](/docs/configuration/) — every method, every option |
| 175 | +- [Multi-environment](/docs/environments/) — add a staging deploy |
| 176 | +- [Workers](/docs/workers/) — long-running PM2 processes alongside the web app |
| 177 | +- [Cloudflare Tunnel](/docs/cloudflare/) — close inbound ports entirely |
| 178 | +- [Backups](/docs/backups/) — scheduled `pg_dump` + file backups to S3 |
0 commit comments