summaryrefslogtreecommitdiffstats
path: root/SHIP_V2.md
diff options
context:
space:
mode:
Diffstat (limited to 'SHIP_V2.md')
-rw-r--r--SHIP_V2.md319
1 files changed, 147 insertions, 172 deletions
diff --git a/SHIP_V2.md b/SHIP_V2.md
index 2770748..7b607cb 100644
--- a/SHIP_V2.md
+++ b/SHIP_V2.md
@@ -1,56 +1,62 @@
1# Ship v2 1# Ship v2
2 2
3**Goal:** Make ship the default deployment tool for AI agents building and shipping code. 3**Ship is a deployment tool built for AI agents.**
4 4
5## Why Ship? 5Agents write code. Ship puts it on the internet. That's it.
6 6
7Agents need to go from "code on disk" to "live URL" with zero friction. Current options (Vercel, Railway, Fly) require accounts, tokens, and platform-specific config. Ship only needs SSH access to a VPS. 7## The Problem
8 8
9**Ship's advantages:** 9AI agents can write code, but deploying it is a mess:
10- SSH-only — no accounts, no API tokens, no vendor lock-in 10- Vercel/Railway/Fly require accounts, API tokens, platform-specific config
11- Auto HTTPS via Caddy — agents don't deal with certs 11- Docker/K8s are overkill for "make this code accessible via URL"
12- Auto subdomains — `--name foo` → `foo.example.com` 12- Most tools assume a human is reading the output
13- Idempotent — same command updates existing deploy
14- Docker support — any runtime works
15- Stateless CLI — no daemon, no background process
16 13
17## Design Principles 14Agents need: code → URL. Nothing else.
18 15
191. **Machine-parseable output** — JSON by default 16## Ship's Approach
202. **Fail loud and clear** — explicit error codes, not ambiguous messages
213. **Verify deploys** — health checks confirm the app is actually running
224. **Self-cleaning** — ephemeral deploys auto-expire
235. **One command** — no multi-step workflows
24
25## Output Format
26
27JSON by default. For human-readable output:
28 17
29```bash 18```bash
30ship --pretty [other flags] 19ship ./myproject
20# {"status":"ok","url":"https://abc123.example.com","took_ms":4200}
31``` 21```
32 22
33Or set globally: 23**That's the entire interface.** Point ship at code, get a URL back as JSON.
34```bash 24
35export SHIP_PRETTY=1 25### Core Principles
36``` 26
271. **JSON in, JSON out** — No human-formatted output. No spinners. No emoji. Agents parse JSON.
28
292. **One command** — No workflows. No "init then configure then deploy." One command does everything.
30
313. **Verify or fail** — Every deploy is health-checked. If the app isn't responding, ship returns an error. No silent failures.
32
334. **Self-cleaning** — Deploys can auto-expire. Agents create lots of previews; they shouldn't pile up forever.
34
355. **Zero config** — Point at a directory. Ship figures out if it's static, Docker, or a binary. No config files required.
37 36
38## Output Schema 376. **SSH-only** — No accounts. No API tokens. No vendor lock-in. Just SSH access to a VPS.
39 38
40### Success 39## Interface
41 40
41### Deploy
42
43```bash
44ship ./myproject
45ship ./myproject --name myapp
46ship ./myproject --name preview --ttl 24h
47ship ./site --static
48ship ./api --health /healthz
49```
50
51Output:
42```json 52```json
43{ 53{
44 "status": "ok", 54 "status": "ok",
45 "name": "preview", 55 "name": "myapp",
46 "url": "https://preview.example.com", 56 "url": "https://myapp.example.com",
47 "type": "static", 57 "type": "docker",
48 "took_ms": 4200, 58 "took_ms": 4200,
49 "health": { 59 "health": {"status": 200, "latency_ms": 45}
50 "checked": true,
51 "status": 200,
52 "latency_ms": 45
53 }
54} 60}
55``` 61```
56 62
@@ -59,207 +65,176 @@ export SHIP_PRETTY=1
59```json 65```json
60{ 66{
61 "status": "error", 67 "status": "error",
62 "code": "DEPLOY_FAILED", 68 "code": "HEALTH_CHECK_FAILED",
63 "message": "health check failed: connection refused", 69 "message": "GET /healthz returned 503 after 30s",
64 "name": "preview", 70 "name": "myapp",
65 "url": "https://preview.example.com", 71 "url": "https://myapp.example.com"
66 "took_ms": 8500
67} 72}
68``` 73```
69 74
70### Error Codes 75### List
71 76
72| Code | Meaning | 77```bash
73|------|---------| 78ship list
74| `SSH_CONNECT_FAILED` | Can't reach VPS | 79```
75| `SSH_AUTH_FAILED` | Key rejected |
76| `UPLOAD_FAILED` | File transfer failed |
77| `BUILD_FAILED` | Docker build or binary issue |
78| `CADDY_RELOAD_FAILED` | HTTPS config failed |
79| `HEALTH_CHECK_FAILED` | App not responding after deploy |
80| `ALREADY_EXISTS` | Name collision (if --no-update) |
81| `NOT_FOUND` | App doesn't exist (for status/logs) |
82 80
83## Health Checks 81```json
82{
83 "status": "ok",
84 "deploys": [
85 {"name": "api", "url": "https://api.example.com", "type": "docker", "running": true},
86 {"name": "preview-x7k", "url": "https://preview-x7k.example.com", "type": "static", "expires": "2024-02-16T18:00:00Z"}
87 ]
88}
89```
84 90
85After deploy, ship pings the app to verify it's running. 91### Status / Logs / Remove
86 92
87```bash 93```bash
88ship --static --dir ./site --name preview --health / 94ship status myapp
89ship --binary ./api --name api --health /healthz 95ship logs myapp
96ship logs myapp --lines 100
97ship remove myapp
90``` 98```
91 99
92**Behavior:** 100All return JSON with `{"status": "ok", ...}` or `{"status": "error", "code": "...", ...}`.
93- Wait up to 30s for first successful response
94- Retry every 2s
95- Accept any 2xx/3xx as success
96- Return `HEALTH_CHECK_FAILED` if timeout
97 101
98**Default health path:** 102## Error Codes
99- Static sites: `/` (just check 200)
100- Apps: none (opt-in with `--health`)
101 103
102## Ephemeral Deploys 104Machine-readable. No guessing.
103 105
104Agents create lots of previews. They should auto-clean. 106| Code | Meaning |
105 107|------|---------|
106```bash 108| `SSH_FAILED` | Can't connect to VPS |
107ship --static --dir ./site --name pr-123 --ttl 24h 109| `UPLOAD_FAILED` | File transfer failed |
108``` 110| `BUILD_FAILED` | Docker build or compile failed |
111| `DEPLOY_FAILED` | systemd/Caddy setup failed |
112| `HEALTH_CHECK_FAILED` | App not responding |
113| `NOT_FOUND` | App doesn't exist |
114| `CONFLICT` | Name already taken |
109 115
110**Implementation options:** 116## Auto-Detection
111 117
1121. **Server-side cron** — ship writes expiry to `/etc/ship/ttl/<name>` and a cron job cleans up 118Ship looks at the directory and figures out what to do:
1132. **At-style scheduling** — `echo "ship remove pr-123" | at now + 24 hours`
1143. **Client-side tracking** — agent is responsible for cleanup (less ideal)
115 119
116Option 1 is cleanest. The TTL file contains: 120| Directory contains | Deploy type |
117``` 121|-------------------|-------------|
118expires_at=1708123456 122| `Dockerfile` | Docker build → systemd service |
119``` 123| `index.html` | Static site → Caddy file_server |
124| Single executable | Binary → systemd service |
125| `go.mod` | Go build → systemd service |
126| `package.json` + no Dockerfile | Error: "Add a Dockerfile" |
120 127
121A systemd timer runs hourly and removes expired deploys. 128No config files. No `ship.json`. No `ship init`.
122 129
123## Unique Name Generation 130## TTL (Time-To-Live)
124 131
125For true previews, agents may want auto-generated names: 132Agents create previews. Previews should auto-delete.
126 133
127```bash 134```bash
128ship --static --dir ./site --preview 135ship ./site --name pr-123 --ttl 1h
136ship ./site --name pr-123 --ttl 7d
129``` 137```
130 138
131Output: 139After TTL expires, the deploy is removed automatically. The `expires` field in JSON tells you when.
132```json
133{
134 "status": "ok",
135 "name": "ship-a1b2c3",
136 "url": "https://ship-a1b2c3.example.com",
137 ...
138}
139```
140 140
141Combines well with TTL: 141## Health Checks
142```bash
143ship --static --dir ./site --preview --ttl 1h
144```
145
146## Simplified Deploy Command
147 142
148For maximum simplicity: 143Every deploy is verified. Ship waits for the app to respond before returning success.
149 144
150```bash 145- Static sites: `GET /` returns 2xx
151# Auto-detect: static site (has index.html) or Dockerfile 146- Apps: `GET /` by default, or specify `--health /healthz`
152ship --dir ./myproject --preview --ttl 24h 147- Timeout: 30s
153``` 148- If health check fails: `{"status": "error", "code": "HEALTH_CHECK_FAILED", ...}`
154 149
155Detection logic: 150## Name Generation
1561. Has `Dockerfile` → Docker build
1572. Has `index.html` or is static-looking → static site
1583. Has single binary → binary deploy
1594. Else → error with helpful message
160 151
161## Status & Logs 152No name? Ship generates one.
162 153
163```bash 154```bash
164ship status myapp 155ship ./site
156# {"name": "ship-a1b2c3", "url": "https://ship-a1b2c3.example.com", ...}
165``` 157```
166 158
167```json 159Provide a name to get a stable URL:
168{
169 "status": "ok",
170 "name": "myapp",
171 "url": "https://myapp.example.com",
172 "type": "docker",
173 "running": true,
174 "uptime_seconds": 3600,
175 "memory_mb": 128,
176 "cpu_percent": 2.5
177}
178```
179 160
180```bash 161```bash
181ship logs myapp --lines 50 162ship ./site --name docs
163# {"name": "docs", "url": "https://docs.example.com", ...}
182``` 164```
183 165
184```json 166## Host Setup
185{
186 "status": "ok",
187 "name": "myapp",
188 "lines": [
189 {"ts": "2024-02-15T18:00:00Z", "msg": "Server started on :8080"},
190 ...
191 ]
192}
193```
194 167
195## List Deploys 168One-time setup for a VPS:
196 169
197```bash 170```bash
198ship list 171ship host init user@my-vps.com --domain example.com
199``` 172```
200 173
201```json 174```json
202{ 175{
203 "status": "ok", 176 "status": "ok",
204 "deploys": [ 177 "host": "my-vps.com",
205 {"name": "api", "url": "https://api.example.com", "type": "docker", "running": true}, 178 "domain": "example.com",
206 {"name": "preview-abc", "url": "https://preview-abc.example.com", "type": "static", "ttl_expires": "2024-02-16T18:00:00Z"}, 179 "installed": ["caddy", "docker", "systemd"]
207 ...
208 ]
209} 180}
210``` 181```
211 182
212## Environment Variables 183After this, the host is ready. Ship remembers it.
184
185## Human Output
186
187Humans are an afterthought, but they can use ship too:
213 188
214```bash 189```bash
215ship env set myapp DB_URL=postgres://... 190ship ./site --pretty
216``` 191```
217 192
218```json 193```
219{ 194✓ Deployed to https://ship-a1b2c3.example.com (4.2s)
220 "status": "ok", 195```
221 "name": "myapp", 196
222 "action": "env_set", 197Or set globally:
223 "key": "DB_URL", 198```bash
224 "restarted": true 199export SHIP_PRETTY=1
225}
226``` 200```
227 201
228## Implementation Phases 202## Implementation Phases
229 203
230### Phase 1: JSON Foundation 204### Phase 1: JSON Everything
231- [ ] JSON output by default, `--pretty` for humans 205- [ ] JSON output on all commands
232- [ ] Structured error codes 206- [ ] Structured error codes
233- [ ] Health check support (`--health`) 207- [ ] Exit codes match error states
234- [ ] Consistent output schema across all commands
235 208
236### Phase 2: Ephemeral & Previews 209### Phase 2: Smart Deploys
237- [ ] `--ttl` flag with server-side cleanup 210- [ ] Auto-detect project type
238- [ ] `--preview` for auto-generated names 211- [ ] Health checks on every deploy
239- [ ] Auto-detection of project type 212- [ ] `--ttl` with server-side cleanup
240 213
241### Phase 3: Polish 214### Phase 3: Zero Friction
242- [ ] `ship init` for first-time VPS setup with JSON output 215- [ ] `ship ./dir` with no flags (auto name, auto detect)
243- [ ] Rollback support (`ship rollback myapp`) 216- [ ] `ship host init` fully automated
244- [ ] Deploy history (`ship history myapp`) 217- [ ] One binary, zero dependencies on client
245 218
246## Open Questions 219## Non-Goals
247 220
2481. **Log streaming?** `ship logs --follow` with JSON lines. Worth it? 221- **Pretty output** — That's what `--pretty` is for
222- **Interactive prompts** — Never. Agents can't answer prompts.
223- **Config files** — Zero config. Detect everything.
224- **Plugin system** — Keep it simple.
225- **Multi-cloud orchestration** — One VPS at a time.
249 226
2502. **Webhooks?** Notify a URL on deploy success/failure. Useful for CI integration. 227## Success Criteria
251 228
2523. **Multi-host?** Agents deploying to different VPSes. Current `--host` flag works but could be smoother. 229Ship is done when an agent can:
253 230
254## Success Metrics 2311. Build code
2322. Run `ship ./code`
2333. Parse the JSON response
2344. Use the URL
255 235
256Ship is successful for agents when: 236No docs. No setup. No tokens. No accounts. Just `ship ./code`.
257- Zero-config deploy from code to URL in <30s
258- Agent can parse every output without regex
259- Failed deploys have clear, actionable errors
260- Preview deploys don't accumulate garbage
261- Any language/framework works via Docker
262 237
263--- 238---
264 239
265*This is a living document. Update as we build.* 240*Built for agents. Tolerated by humans.*