summaryrefslogtreecommitdiffstats
path: root/SHIP_V2.md
blob: 277074843fc1109855d9a2dcb92482684a1d2e38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# Ship v2

**Goal:** Make ship the default deployment tool for AI agents building and shipping code.

## Why Ship?

Agents 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.

**Ship's advantages:**
- SSH-only — no accounts, no API tokens, no vendor lock-in
- Auto HTTPS via Caddy — agents don't deal with certs
- Auto subdomains — `--name foo` → `foo.example.com`
- Idempotent — same command updates existing deploy
- Docker support — any runtime works
- Stateless CLI — no daemon, no background process

## Design Principles

1. **Machine-parseable output** — JSON by default
2. **Fail loud and clear** — explicit error codes, not ambiguous messages
3. **Verify deploys** — health checks confirm the app is actually running
4. **Self-cleaning** — ephemeral deploys auto-expire
5. **One command** — no multi-step workflows

## Output Format

JSON by default. For human-readable output:

```bash
ship --pretty [other flags]
```

Or set globally:
```bash
export SHIP_PRETTY=1
```

## Output Schema

### Success

```json
{
  "status": "ok",
  "name": "preview",
  "url": "https://preview.example.com",
  "type": "static",
  "took_ms": 4200,
  "health": {
    "checked": true,
    "status": 200,
    "latency_ms": 45
  }
}
```

### Error

```json
{
  "status": "error",
  "code": "DEPLOY_FAILED",
  "message": "health check failed: connection refused",
  "name": "preview",
  "url": "https://preview.example.com",
  "took_ms": 8500
}
```

### Error Codes

| Code | Meaning |
|------|---------|
| `SSH_CONNECT_FAILED` | Can't reach VPS |
| `SSH_AUTH_FAILED` | Key rejected |
| `UPLOAD_FAILED` | File transfer failed |
| `BUILD_FAILED` | Docker build or binary issue |
| `CADDY_RELOAD_FAILED` | HTTPS config failed |
| `HEALTH_CHECK_FAILED` | App not responding after deploy |
| `ALREADY_EXISTS` | Name collision (if --no-update) |
| `NOT_FOUND` | App doesn't exist (for status/logs) |

## Health Checks

After deploy, ship pings the app to verify it's running.

```bash
ship --static --dir ./site --name preview --health /
ship --binary ./api --name api --health /healthz
```

**Behavior:**
- Wait up to 30s for first successful response
- Retry every 2s
- Accept any 2xx/3xx as success
- Return `HEALTH_CHECK_FAILED` if timeout

**Default health path:**
- Static sites: `/` (just check 200)
- Apps: none (opt-in with `--health`)

## Ephemeral Deploys

Agents create lots of previews. They should auto-clean.

```bash
ship --static --dir ./site --name pr-123 --ttl 24h
```

**Implementation options:**

1. **Server-side cron** — ship writes expiry to `/etc/ship/ttl/<name>` and a cron job cleans up
2. **At-style scheduling** — `echo "ship remove pr-123" | at now + 24 hours`
3. **Client-side tracking** — agent is responsible for cleanup (less ideal)

Option 1 is cleanest. The TTL file contains:
```
expires_at=1708123456
```

A systemd timer runs hourly and removes expired deploys.

## Unique Name Generation

For true previews, agents may want auto-generated names:

```bash
ship --static --dir ./site --preview
```

Output:
```json
{
  "status": "ok",
  "name": "ship-a1b2c3",
  "url": "https://ship-a1b2c3.example.com",
  ...
}
```

Combines well with TTL:
```bash
ship --static --dir ./site --preview --ttl 1h
```

## Simplified Deploy Command

For maximum simplicity:

```bash
# Auto-detect: static site (has index.html) or Dockerfile
ship --dir ./myproject --preview --ttl 24h
```

Detection logic:
1. Has `Dockerfile` → Docker build
2. Has `index.html` or is static-looking → static site
3. Has single binary → binary deploy
4. Else → error with helpful message

## Status & Logs

```bash
ship status myapp
```

```json
{
  "status": "ok",
  "name": "myapp",
  "url": "https://myapp.example.com",
  "type": "docker",
  "running": true,
  "uptime_seconds": 3600,
  "memory_mb": 128,
  "cpu_percent": 2.5
}
```

```bash
ship logs myapp --lines 50
```

```json
{
  "status": "ok",
  "name": "myapp",
  "lines": [
    {"ts": "2024-02-15T18:00:00Z", "msg": "Server started on :8080"},
    ...
  ]
}
```

## List Deploys

```bash
ship list
```

```json
{
  "status": "ok",
  "deploys": [
    {"name": "api", "url": "https://api.example.com", "type": "docker", "running": true},
    {"name": "preview-abc", "url": "https://preview-abc.example.com", "type": "static", "ttl_expires": "2024-02-16T18:00:00Z"},
    ...
  ]
}
```

## Environment Variables

```bash
ship env set myapp DB_URL=postgres://...
```

```json
{
  "status": "ok",
  "name": "myapp",
  "action": "env_set",
  "key": "DB_URL",
  "restarted": true
}
```

## Implementation Phases

### Phase 1: JSON Foundation
- [ ] JSON output by default, `--pretty` for humans
- [ ] Structured error codes
- [ ] Health check support (`--health`)
- [ ] Consistent output schema across all commands

### Phase 2: Ephemeral & Previews
- [ ] `--ttl` flag with server-side cleanup
- [ ] `--preview` for auto-generated names
- [ ] Auto-detection of project type

### Phase 3: Polish
- [ ] `ship init` for first-time VPS setup with JSON output
- [ ] Rollback support (`ship rollback myapp`)
- [ ] Deploy history (`ship history myapp`)

## Open Questions

1. **Log streaming?** `ship logs --follow` with JSON lines. Worth it?

2. **Webhooks?** Notify a URL on deploy success/failure. Useful for CI integration.

3. **Multi-host?** Agents deploying to different VPSes. Current `--host` flag works but could be smoother.

## Success Metrics

Ship is successful for agents when:
- Zero-config deploy from code to URL in <30s
- Agent can parse every output without regex
- Failed deploys have clear, actionable errors
- Preview deploys don't accumulate garbage
- Any language/framework works via Docker

---

*This is a living document. Update as we build.*