summaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md350
1 files changed, 207 insertions, 143 deletions
diff --git a/README.md b/README.md
index 4394e35..54be72f 100644
--- a/README.md
+++ b/README.md
@@ -1,219 +1,283 @@
1# Ship - VPS Deployment CLI 1# Ship
2 2
3Simple CLI tool for deploying Go apps and static sites to a VPS with automatic HTTPS via Caddy. 3Ship deploys apps and static sites to a VPS over SSH. It handles HTTPS certificates, port allocation, systemd services, and reverse proxying — all with zero dependencies beyond SSH access.
4 4
5## Features 5There are two deployment modes:
6 6
7- Single command deployment from your laptop 7- **Git push** — push to a bare repo on the VPS, which triggers a Docker build and deploy via post-receive hooks. Deployment config (systemd unit, Caddyfile) lives in `.ship/` in your repo and is versioned alongside code.
8- Automatic HTTPS via Caddy + Let's Encrypt 8- **Direct** — SCP a pre-built binary or rsync a static directory to the VPS. Ship generates and installs the systemd unit and Caddy config on your behalf.
9- Automatic port allocation (no manual tracking)
10- Environment variable management
11- Systemd process management with auto-restart
12- Support for multiple apps/sites on one VPS
13- State stored locally (VPS is stateless and easily recreatable)
14- Zero dependencies on VPS (just installs Caddy)
15 9
16## Installation 10If a base domain is configured, Ship also serves **Go vanity imports** and **git HTTPS cloning** from the same domain, so `go get yourdomain.com/foo` works with zero extra setup.
17 11
18```bash 12## Install
19# Build the CLI
20go build -o ~/bin/ship ./cmd/ship
21 13
22# Or install to GOPATH 14```
23go install ./cmd/ship 15go install github.com/bdw/ship/cmd/ship@latest
24``` 16```
25 17
26## Quick Start 18Or build from source:
27
28### 1. Initialize Your VPS (One-time)
29 19
30```bash 20```
31# Initialize a fresh VPS (this sets it as the default host) 21go build -o ship ./cmd/ship
32ship host init user@your-vps-ip
33``` 22```
34 23
35This will: 24## Quick start
36- Install Caddy
37- Configure Caddy for automatic HTTPS
38- Create necessary directories
39- Set up the VPS for deployments
40 25
41### 2. Deploy a Go App 26### 1. Set up the VPS
42 27
43```bash 28```
44# Build your app for Linux 29ship host init --host user@your-vps --base-domain example.com
45GOOS=linux GOARCH=amd64 go build -o myapp 30```
46 31
47# Deploy it 32This installs Caddy, Docker, git, and fcgiwrap. It creates a `git` user for push access, configures sudoers for deploy hooks, sets up vanity import serving, and enables automatic HTTPS. The host becomes the default for subsequent commands.
48ship --binary ./myapp --domain api.example.com
49 33
50# With environment variables 34If you don't need git-push deploys or vanity imports, omit `--base-domain`:
51ship --binary ./myapp --domain api.example.com \
52 --env DB_HOST=localhost \
53 --env API_KEY=secret
54 35
55# Or from an env file 36```
56ship --binary ./myapp --domain api.example.com \ 37ship host init --host user@your-vps
57 --env-file .env.production
58``` 38```
59 39
60### 3. Deploy a Static Site 40### 2. Deploy
61 41
62```bash 42**Git push (Docker-based app):**
63# Build your site
64npm run build
65 43
66# Deploy it 44```
67ship --static --dir ./dist --domain example.com 45ship init myapp
68``` 46```
69 47
70## App Requirements 48This creates a bare git repo on the VPS, generates `.ship/Caddyfile` and `.ship/service` locally, initializes a local git repo if needed, and adds an `origin` remote.
71 49
72Your Go app must: 50```
731. Listen on HTTP (not HTTPS - Caddy handles that) 51git add .ship/ Dockerfile
742. Accept port via `--port` flag or `PORT` environment variable 52git commit -m "initial deploy"
753. Bind to `localhost` or `127.0.0.1` only 53git push origin main
54```
76 55
77Example: 56The post-receive hook checks out code, installs `.ship/` configs, runs `docker build`, and restarts the service. If no Dockerfile is present, the push is accepted but deploy is skipped — useful for Go modules and libraries that only need vanity imports.
78 57
79```go 58**Git push (static site):**
80package main
81 59
82import ( 60```
83 "flag" 61ship init mysite --static
84 "fmt" 62git add .ship/ index.html
85 "net/http" 63git commit -m "initial deploy"
86 "os" 64git push origin main
87) 65```
88 66
89func main() { 67**Direct (pre-built binary):**
90 port := flag.String("port", os.Getenv("PORT"), "port to listen on")
91 flag.Parse()
92 68
93 if *port == "" { 69```
94 *port = "8080" // fallback for local dev 70GOOS=linux GOARCH=amd64 go build -o myapp
95 } 71ship --binary ./myapp --domain api.example.com
72```
96 73
97 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 74**Direct (static site):**
98 w.Write([]byte("Hello World"))
99 })
100 75
101 addr := "127.0.0.1:" + *port 76```
102 fmt.Printf("Listening on %s\n", addr) 77ship --static --dir ./dist --domain example.com
103 http.ListenAndServe(addr, nil)
104}
105``` 78```
106 79
107## Commands 80## Commands
108 81
109### Host Management 82### `ship init <name>`
83
84Create a bare git repo on the VPS and generate local `.ship/` config files.
85
86```
87ship init myapp # Docker-based app
88ship init mysite --static # static site
89ship init myapp --domain custom.example.com # custom domain
90ship init mylib --public # publicly cloneable (for go get)
91```
110 92
111```bash 93Flags:
112# Initialize a fresh VPS (one-time setup, sets as default) 94- `--static` — initialize as a static site instead of a Docker app
113ship host init user@vps-ip 95- `--public` — make the repo publicly cloneable over HTTPS
96- `--domain` — custom domain (default: `name.basedomain`)
114 97
115# Update system packages (apt update && apt upgrade) 98### `ship deploy <name>`
116ship host update
117 99
118# Check host status 100Manually rebuild and deploy a git-deployed app. Runs the same steps as the post-receive hook: checkout, install configs, docker build, restart.
119ship host status
120 101
121# SSH into the host
122ship host ssh
123``` 102```
103ship deploy myapp
104```
105
106### `ship [deploy flags]`
124 107
125### Deploy App/Site 108Deploy a pre-built binary or static directory directly.
126```bash 109
127# Go app 110```
111# App
128ship --binary ./myapp --domain api.example.com 112ship --binary ./myapp --domain api.example.com
113ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost --env API_KEY=secret
114ship --binary ./myapp --domain api.example.com --env-file .env.production
115ship --binary ./myapp --name myapi --memory 512M --cpu 50%
129 116
130# Static site 117# Static site
131ship --static --dir ./dist --domain example.com 118ship --static --dir ./dist --domain example.com
132 119
133# Custom name (defaults to binary/directory name) 120# Config update (no binary, just change settings)
134ship --name myapi --binary ./myapp --domain api.example.com 121ship --name myapi --memory 1G
122ship --name myapi --env DEBUG=true
135``` 123```
136 124
137### List Deployments 125Flags:
138```bash 126- `--binary` — path to a compiled binary
139ship list 127- `--static` — deploy as a static site
128- `--dir` — directory to deploy (default: `.`)
129- `--domain` — custom domain
130- `--name` — app name (default: inferred from binary or directory)
131- `--env KEY=VALUE` — environment variable (repeatable)
132- `--env-file` — path to a `.env` file
133- `--args` — arguments passed to the binary
134- `--file` — config file to upload to working directory (repeatable)
135- `--memory` — memory limit (e.g., `512M`, `1G`)
136- `--cpu` — CPU limit (e.g., `50%`, `200%` for 2 cores)
137
138### `ship list`
139
140List all deployments on the default host.
141
142```
143NAME TYPE VISIBILITY DOMAIN PORT
144myapp git-app private myapp.example.com :8001
145mysite git-static public mysite.example.com
146api app api.example.com :8002
140``` 147```
141 148
142### Manage Deployments 149### `ship status <name>`
143```bash
144# View logs
145ship logs myapp
146 150
147# View status 151Show systemd service status for an app.
148ship status myapp
149 152
150# Restart app 153### `ship logs <name>`
151ship restart myapp
152 154
153# Remove deployment 155Show service logs (via journalctl).
154ship remove myapp 156
155``` 157### `ship restart <name>`
158
159Restart an app's systemd service.
160
161### `ship remove <name>`
156 162
157### Environment Variables 163Remove a deployment. Stops the service, removes files, configs, and state.
158```bash
159# View current env vars (secrets are masked)
160ship env list myapi
161 164
162# Set env vars 165### `ship env`
163ship env set myapi DB_HOST=localhost API_KEY=secret
164 166
165# Load from file 167Manage environment variables for an app.
166ship env set myapi -f .env.production
167 168
168# Unset env var 169```
169ship env unset myapi API_KEY 170ship env list myapp # show env vars (secrets masked)
171ship env set myapp KEY=VALUE # set variable(s)
172ship env set myapp -f .env # load from file
173ship env unset myapp KEY # remove a variable
170``` 174```
171 175
172## Configuration 176### `ship host`
173 177
174The host you initialize becomes the default, so you don't need to specify `--host` for every command. The default host is stored in `~/.config/ship/state.json`. 178Manage the VPS.
175 179
176## How It Works 180```
181ship host init --host user@vps --base-domain example.com # one-time setup
182ship host status # uptime, disk, memory, load
183ship host update # apt update && upgrade
184ship host ssh # open an SSH session
185ship host set-domain example.com # change base domain
186```
177 187
1781. **State on Laptop**: All deployment state lives at `~/.config/ship/state.json` on your laptop 188### `ship ui`
1792. **SSH Orchestration**: The CLI uses SSH to run commands on your VPS
1803. **File Transfer**: Binaries transferred via SCP, static sites via rsync
1814. **Caddy for HTTPS**: Caddy automatically handles HTTPS certificates
1825. **Systemd for Processes**: Apps run as systemd services with auto-restart
1836. **Dumb VPS**: The VPS is stateless - you can recreate it by redeploying from local state
184 189
185## File Structure 190Launch a local web UI for viewing deployments.
186 191
187### On Laptop
188``` 192```
189~/.config/ship/state.json # All deployment state (including default host) 193ship ui # http://localhost:8080
194ship ui -p 3000 # custom port
190``` 195```
191 196
192### On VPS 197### `ship version`
198
199Show version, commit, and build date.
200
201## How it works
202
203### Architecture
204
205Ship is a client-side CLI. All state lives on your laptop at `~/.config/ship/state.json`. The VPS is configured entirely over SSH — no agent or daemon runs on the server. This means the VPS is stateless and easily recreatable from local state.
206
207### Git push flow
208
2091. `ship init` creates a bare repo at `/srv/git/<name>.git` with a post-receive hook
2102. `git push` triggers the hook, which:
211 - Checks out code to `/var/lib/<name>/src`
212 - Copies `.ship/service` to `/etc/systemd/system/<name>.service`
213 - Copies `.ship/Caddyfile` to `/etc/caddy/sites-enabled/<name>.caddy`
214 - Runs `docker build` (skipped if no Dockerfile)
215 - Restarts the systemd service
2163. The Docker container runs with:
217 - Port bound to `127.0.0.1:<port>`
218 - Env vars from `/etc/ship/env/<name>.env`
219 - Persistent data volume at `/var/lib/<name>/data` (mounted as `/data`)
2204. Caddy reverse-proxies HTTPS traffic to the container
221
222### Direct deploy flow
223
2241. Binary uploaded via SCP to `/usr/local/bin/<name>`
2252. A dedicated system user is created
2263. Ship generates and installs a systemd unit and Caddy config
2274. Service is started, Caddy is reloaded
228
229### Vanity imports and git cloning
230
231When a base domain is configured, Ship sets up the base domain to serve:
232
233- **Go vanity imports** — `go get example.com/myapp` returns the correct `<meta go-import>` tag pointing to `https://example.com/myapp.git`
234- **Git HTTPS cloning** — `git clone https://example.com/myapp.git` works for public repos (those created with `--public`)
235
236This is handled by Caddy's `templates` directive and `fcgiwrap` + `git-http-backend`, with no custom server.
237
238### Port allocation
239
240Ports are allocated automatically starting from 8001 and never reused. You don't need to track which ports are in use.
241
242## VPS file layout
243
193``` 244```
194/usr/local/bin/myapp # Go binary 245/srv/git/<name>.git/ # bare git repos
195/var/lib/myapp/ # Working directory 246/srv/git/<name>.git/hooks/post-receive # auto-deploy hook
196/etc/systemd/system/myapp.service # Systemd unit 247
197/etc/caddy/sites-enabled/myapp.caddy # Caddy config 248/var/lib/<name>/src/ # checked-out source (for docker build)
198/etc/ship/env/myapp.env # Environment variables 249/var/lib/<name>/data/ # persistent data volume
250/etc/systemd/system/<name>.service # systemd unit
251/etc/caddy/sites-enabled/<name>.caddy # Caddy config
252/etc/ship/env/<name>.env # environment variables
199 253
200/var/www/mysite/ # Static site files 254/var/www/<name>/ # static site files
201/etc/caddy/sites-enabled/mysite.caddy # Caddy config 255
256/usr/local/bin/<name> # direct-deployed binaries
257
258/opt/ship/vanity/index.html # vanity import template
259/etc/caddy/sites-enabled/ship-code.caddy # base domain Caddy config
260/etc/sudoers.d/ship-git # sudo rules for git user
261/home/git/.ssh/authorized_keys # SSH keys for git push
202``` 262```
203 263
204## Security 264## App requirements
265
266For direct-deployed Go apps, the binary must:
205 267
206- Each Go app runs as dedicated system user 2681. Listen on HTTP (Caddy handles HTTPS)
207- Systemd security hardening enabled (NoNewPrivileges, PrivateTmp) 2692. Read the port from the `PORT` environment variable or a `--port` flag
208- Static sites served as www-data 2703. Bind to `127.0.0.1` (not `0.0.0.0`)
209- Caddy automatically manages TLS certificates
210- Environment files stored with 0600 permissions
211- Secrets masked when displaying environment variables
212 271
213## Supported OS 272For git-deployed Docker apps, the Dockerfile should expose a service that listens on the port specified by the `PORT` environment variable.
273
274## Supported platforms
275
276VPS: Ubuntu 20.04+ or Debian 11+
277
278## Security
214 279
215- Ubuntu 20.04+ 280See [SECURITY.md](SECURITY.md) for the threat model, mitigations, and known gaps.
216- Debian 11+
217 281
218## License 282## License
219 283