summaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md202
1 files changed, 40 insertions, 162 deletions
diff --git a/README.md b/README.md
index 54be72f..cdd4127 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
1# Ship 1# Ship
2 2
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. 3Ship turns a VPS into a self-hosted git server with a web frontend — similar to running cgit on your own domain. Visiting your base domain in a browser shows a cgit repo index; clicking through shows trees, commit logs, diffs, and blame. Public repos are cloneable over HTTPS. If you host Go code, `go get` works with your domain out of the box. This is the read-only, public-facing side, and it works exactly the way cgit users expect.
4 4
5There are two deployment modes: 5The difference is what happens on the write side. When you `git push` over SSH, Ship doesn't just update the bare repo — it builds and deploys your code. A post-receive hook checks out the repo, runs `docker build`, installs a systemd service and Caddy reverse-proxy config, and restarts the app. Your deployment config (the systemd unit, the Caddyfile) lives in `.ship/` in your repo and is versioned alongside your code. Push to main and it's live; push to any other branch and nothing happens.
6 6
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. 7Not every repo needs to be a running service. If there's no Dockerfile, the push is accepted but the deploy step is skipped — the repo just sits there as a browsable, cloneable library. This makes Ship useful for Go modules that only need vanity imports and a public source view, alongside apps that need the full build-and-deploy pipeline. The same base domain serves both.
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 8
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. 9Ship also supports direct deploys (SCP a binary or rsync a static directory) for cases where git push isn't the right fit.
10
11Ship 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.
11 12
12## Install 13## Install
13 14
@@ -29,13 +30,7 @@ go build -o ship ./cmd/ship
29ship host init --host user@your-vps --base-domain example.com 30ship host init --host user@your-vps --base-domain example.com
30``` 31```
31 32
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. 33This installs Caddy, Docker, git, fcgiwrap, and cgit. It creates a `git` user for push access, configures sudoers for deploy hooks, and enables automatic HTTPS. The host becomes the default for subsequent commands.
33
34If you don't need git-push deploys or vanity imports, omit `--base-domain`:
35
36```
37ship host init --host user@your-vps
38```
39 34
40### 2. Deploy 35### 2. Deploy
41 36
@@ -43,18 +38,11 @@ ship host init --host user@your-vps
43 38
44``` 39```
45ship init myapp 40ship init myapp
46```
47
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.
49
50```
51git add .ship/ Dockerfile 41git add .ship/ Dockerfile
52git commit -m "initial deploy" 42git commit -m "initial deploy"
53git push origin main 43git push origin main
54``` 44```
55 45
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.
57
58**Git push (static site):** 46**Git push (static site):**
59 47
60``` 48```
@@ -64,19 +52,26 @@ git commit -m "initial deploy"
64git push origin main 52git push origin main
65``` 53```
66 54
67**Direct (pre-built binary):** 55**Git push (library / Go module):**
68 56
69``` 57```
70GOOS=linux GOARCH=amd64 go build -o myapp 58ship init mylib --public
71ship --binary ./myapp --domain api.example.com 59git add .
60git commit -m "initial"
61git push origin main
72``` 62```
73 63
74**Direct (static site):** 64No Dockerfile, so nothing is deployed — the repo is just browsable and cloneable at `https://example.com/mylib`.
65
66**Direct (pre-built binary):**
75 67
76``` 68```
77ship --static --dir ./dist --domain example.com 69GOOS=linux GOARCH=amd64 go build -o myapp
70ship --binary ./myapp --domain api.example.com
78``` 71```
79 72
73You can version control `.ship/` or add it to `.gitignore` — it's your choice.
74
80## Commands 75## Commands
81 76
82### `ship init <name>` 77### `ship init <name>`
@@ -84,193 +79,76 @@ ship --static --dir ./dist --domain example.com
84Create a bare git repo on the VPS and generate local `.ship/` config files. 79Create a bare git repo on the VPS and generate local `.ship/` config files.
85 80
86``` 81```
87ship init myapp # Docker-based app 82ship init myapp # Docker-based app
88ship init mysite --static # static site 83ship init mysite --static # static site
89ship init myapp --domain custom.example.com # custom domain 84ship init myapp --domain custom.example.com # custom domain
90ship init mylib --public # publicly cloneable (for go get) 85ship init mylib --public # publicly cloneable (for go get)
91``` 86```
92 87
93Flags:
94- `--static` — initialize as a static site instead of a Docker app
95- `--public` — make the repo publicly cloneable over HTTPS
96- `--domain` — custom domain (default: `name.basedomain`)
97
98### `ship deploy <name>` 88### `ship deploy <name>`
99 89
100Manually rebuild and deploy a git-deployed app. Runs the same steps as the post-receive hook: checkout, install configs, docker build, restart. 90Manually rebuild and deploy a git-deployed app.
101
102```
103ship deploy myapp
104```
105 91
106### `ship [deploy flags]` 92### `ship [deploy flags]`
107 93
108Deploy a pre-built binary or static directory directly. 94Deploy a pre-built binary or static directory directly.
109 95
110``` 96```
111# App
112ship --binary ./myapp --domain api.example.com 97ship --binary ./myapp --domain api.example.com
113ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost --env API_KEY=secret 98ship --binary ./myapp --domain api.example.com --env DB_HOST=localhost
114ship --binary ./myapp --domain api.example.com --env-file .env.production
115ship --binary ./myapp --name myapi --memory 512M --cpu 50%
116
117# Static site
118ship --static --dir ./dist --domain example.com 99ship --static --dir ./dist --domain example.com
119 100ship --name myapi --memory 512M --cpu 50%
120# Config update (no binary, just change settings)
121ship --name myapi --memory 1G
122ship --name myapi --env DEBUG=true
123``` 101```
124 102
125Flags: 103Flags: `--binary`, `--static`, `--dir`, `--domain`, `--name`, `--env`, `--env-file`, `--args`, `--file`, `--memory`, `--cpu`
126- `--binary` — path to a compiled binary
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 104
138### `ship list` 105### `ship list`
139 106
140List all deployments on the default host. 107List all deployments on the default host.
141 108
142``` 109### `ship status/logs/restart/remove <name>`
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
147```
148
149### `ship status <name>`
150
151Show systemd service status for an app.
152
153### `ship logs <name>`
154
155Show service logs (via journalctl).
156
157### `ship restart <name>`
158
159Restart an app's systemd service.
160
161### `ship remove <name>`
162 110
163Remove a deployment. Stops the service, removes files, configs, and state. 111Manage a deployment's systemd service.
164 112
165### `ship env` 113### `ship env`
166 114
167Manage environment variables for an app.
168
169``` 115```
170ship env list myapp # show env vars (secrets masked) 116ship env list myapp
171ship env set myapp KEY=VALUE # set variable(s) 117ship env set myapp KEY=VALUE
172ship env set myapp -f .env # load from file 118ship env unset myapp KEY
173ship env unset myapp KEY # remove a variable
174``` 119```
175 120
176### `ship host` 121### `ship host`
177 122
178Manage the VPS.
179
180``` 123```
181ship host init --host user@vps --base-domain example.com # one-time setup 124ship host init --host user@vps --base-domain example.com
182ship host status # uptime, disk, memory, load 125ship host status
183ship host update # apt update && upgrade 126ship host update
184ship host ssh # open an SSH session 127ship host ssh
185ship host set-domain example.com # change base domain
186``` 128```
187 129
188### `ship ui` 130### `ship ui`
189 131
190Launch a local web UI for viewing deployments. 132Launch a local web UI for viewing deployments.
191 133
192```
193ship ui # http://localhost:8080
194ship ui -p 3000 # custom port
195```
196
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 134## VPS file layout
243 135
244``` 136```
245/srv/git/<name>.git/ # bare git repos 137/srv/git/<name>.git/ # bare git repos
246/srv/git/<name>.git/hooks/post-receive # auto-deploy hook 138/srv/git/<name>.git/hooks/post-receive # auto-deploy hook
247
248/var/lib/<name>/src/ # checked-out source (for docker build) 139/var/lib/<name>/src/ # checked-out source (for docker build)
249/var/lib/<name>/data/ # persistent data volume 140/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
253
254/var/www/<name>/ # static site files 141/var/www/<name>/ # static site files
255 142/etc/systemd/system/<name>.service # systemd unit
256/usr/local/bin/<name> # direct-deployed binaries 143/etc/caddy/sites-enabled/<name>.caddy # per-app Caddy config
257
258/opt/ship/vanity/index.html # vanity import template
259/etc/caddy/sites-enabled/ship-code.caddy # base domain Caddy config 144/etc/caddy/sites-enabled/ship-code.caddy # base domain Caddy config
145/etc/cgitrc # cgit configuration
146/etc/ship/env/<name>.env # environment variables
260/etc/sudoers.d/ship-git # sudo rules for git user 147/etc/sudoers.d/ship-git # sudo rules for git user
148/opt/ship/vanity/index.html # vanity import template
261/home/git/.ssh/authorized_keys # SSH keys for git push 149/home/git/.ssh/authorized_keys # SSH keys for git push
262``` 150```
263 151
264## App requirements
265
266For direct-deployed Go apps, the binary must:
267
2681. Listen on HTTP (Caddy handles HTTPS)
2692. Read the port from the `PORT` environment variable or a `--port` flag
2703. Bind to `127.0.0.1` (not `0.0.0.0`)
271
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 152## Supported platforms
275 153
276VPS: Ubuntu 20.04+ or Debian 11+ 154VPS: Ubuntu 20.04+ or Debian 11+