Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

DEPLOY.md

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

DEPLOY.mdBlame134 lines · 1 contributor
988380aClaude1# Deploying Gluecron
8ade77bClaude2
988380aClaude3## The green ecosystem
8ade77bClaude4
988380aClaude5Gluecron is part of a self-hosting triangle. Each service deploys or observes the others:
8ade77bClaude6
988380aClaude7- **Crontech** is the deployment platform for Gluecron (and everything else).
8- **Gluecron** hosts the source code for Crontech, GateTest, and itself.
9- **GateTest** scans every push to Gluecron.
3644238Claude10
988380aClaude11Dogfood end-to-end. **Crontech is the deployment target.** Do not deploy Gluecron to Vercel or Hetzner — they are not part of this stack.
3644238Claude12
988380aClaude13---
3644238Claude14
988380aClaude15## 1. Prerequisites
3644238Claude16
988380aClaude171. **Neon Postgres** — create a project at https://neon.tech and copy the pooled connection string. This becomes `DATABASE_URL`.
182. **Crontech account** — tenant + service permissions for the repo.
193. **(Recommended) Anthropic API key** — https://console.anthropic.com. Everything AI-flavoured degrades to safe fallbacks without it, but you'll want this for the differentiator features.
3644238Claude20
988380aClaude21---
8ade77bClaude22
988380aClaude23## 2. Deploy via Crontech
8ade77bClaude24
988380aClaude25In Crontech, create a service pointing at this repo with:
8ade77bClaude26
988380aClaude27- **Build:** `bun install --production`
28- **Release:** `bun run db:migrate`
29- **Start:** `bun run src/index.ts`
30- **Port:** `3000`
31- **Persistent volume:** mount `/data/repos` (bare git repos live on disk)
8ade77bClaude32
988380aClaude33Route a subdomain (e.g. `gluecron.crontech.ai`) or a custom domain (e.g. `gluecron.com` via CNAME) to the service.
8ade77bClaude34
988380aClaude35Crontech handles TLS termination, rollouts, and restart policy. Treat it as the primary control plane — do not bolt on nginx, systemd, or Docker Compose orchestration.
8ade77bClaude36
988380aClaude37---
8ade77bClaude38
988380aClaude39## 3. Environment variables
8ade77bClaude40
988380aClaude41Full reference is in [`.env.example`](./.env.example); cross-reference BUILD_BIBLE §5.2 for anything not listed here.
8ade77bClaude42
988380aClaude43### Required
44| Variable | Purpose |
45|---|---|
46| `DATABASE_URL` | Neon Postgres connection string (pooled). |
47| `GIT_REPOS_PATH` | Where bare repos live on disk (e.g. `/data/repos`). |
48| `PORT` | HTTP port. Default `3000`. |
49| `APP_BASE_URL` | Canonical public URL, used when composing outbound emails and webhooks. |
8ade77bClaude50
988380aClaude51### Strongly recommended
52| Variable | Purpose |
53|---|---|
54| `ANTHROPIC_API_KEY` | Unlocks AI review, triage, chat, incident responder, auto-repair, completions. |
55| `EMAIL_PROVIDER` | `log` (default, writes to stderr) or `resend` (production). |
56| `EMAIL_FROM` | Sender identity on outbound mail. |
57| `RESEND_API_KEY` | Required when `EMAIL_PROVIDER=resend`. |
58| `VOYAGE_API_KEY` | Upgrades semantic search to Voyage `voyage-code-3` embeddings; without it, a deterministic hashing embedder is used. |
59
60### Integrations
61| Variable | Purpose |
62|---|---|
63| `GATETEST_URL` | Outbound push webhook. Default `https://gatetest.ai/api/events/push`. |
64| `GATETEST_API_KEY` | Bearer sent on outbound GateTest posts. |
65| `GATETEST_CALLBACK_SECRET` | Inbound bearer GateTest must present on result callbacks. See `GATETEST_HOOK.md`. |
66| `GATETEST_HMAC_SECRET` | HMAC secret for inbound GateTest callbacks (alternative to bearer). |
67| `CRONTECH_DEPLOY_URL` | Outbound deploy webhook. Default `https://crontech.ai/api/hooks/gluecron/push`. |
68| `GLUECRON_WEBHOOK_SECRET` | Bearer sent on the outbound Crontech deploy webhook. |
69| `CRONTECH_EVENT_TOKEN` | Bearer Crontech must present on `deploy.succeeded` / `deploy.failed` callbacks to `POST /api/events/deploy`. |
70| `CRONTECH_STATUS_URL` / `GLUECRON_STATUS_URL` / `GATETEST_STATUS_URL` | Platform-status endpoints surfaced in the `/admin/platform` widget. |
8ade77bClaude71
988380aClaude72### Operational flags
73| Variable | Purpose |
74|---|---|
75| `AUTOPILOT_DISABLED=1` | Opt out of the 5-minute autopilot ticker (mirror sync, merge-queue processing, weekly digests, advisory rescans). Default: enabled. |
76| `DEMO_SEED_ON_BOOT=1` | Idempotently create a `demo` user plus three public sample repos (`hello-python`, `todo-api`, `design-docs`) on server start. Safe to leave enabled — a second run is a near-instant no-op. Site admins can also trigger a reseed manually from `/admin`. |
77
78---
79
80## 4. Database migrations
81
82Migrations are checked into `drizzle/` and run via:
8ade77bClaude83
84```bash
988380aClaude85bun run db:migrate
8ade77bClaude86```
87
988380aClaude88Hook this into Crontech's release phase so every deploy self-migrates. First-time bootstrap: the release command will pick up `0000_init.sql` through the latest migration in order.
8ade77bClaude89
988380aClaude90---
8ade77bClaude91
988380aClaude92## 5. Post-deploy checklist
8ade77bClaude93
988380aClaude94Verify these in order after the first deploy:
8ade77bClaude95
988380aClaude96- [ ] **Migrations ran** — `db:migrate` reported success in release logs.
97- [ ] **`/healthz` is green** — returns `{"ok": true, ...}` with a 200.
98- [ ] **`/readyz` is green** — returns `{"ok": true}`, confirming DB connectivity.
99- [ ] **`/metrics` responds** — basic process snapshot is emitted.
100- [ ] **First admin bootstrap** — register the first account at `/register`. Per `src/lib/admin.ts`, while `site_admins` is empty the **oldest user** is treated as site admin. Register the intended admin account first so the bootstrap rule applies; subsequent admins can then be granted from `/admin/users`.
101- [ ] **Smart HTTP works** — create a repo at `/new`, then `git clone https://<your-host>/<user>/<repo>.git` round-trips.
102- [ ] **Push triggers the pipeline** — a `git push` fires GateTest, the secret scanner, and webhook fan-out (check `/:owner/:repo/gates` and `/:owner/:repo/settings/audit`).
103- [ ] **Custom domain TLS** — certificate issued and `APP_BASE_URL` reflects the canonical host.
104- [ ] **Autopilot state** — if you want the background ticker off, confirm `AUTOPILOT_DISABLED=1` is set; otherwise expect mirror syncs and weekly digests to fire on schedule.
8ade77bClaude105
988380aClaude106---
8ade77bClaude107
988380aClaude108## 6. Operations
8ade77bClaude109
988380aClaude110### Logs
111Crontech streams stdout/stderr from `bun run src/index.ts`. Every request carries an `X-Request-Id` header; grep logs by that ID when tracing a report.
112
113### Restarts
114Trigger from the Crontech dashboard. Rate-limit counters are in-memory and reset on restart — that is intentional.
115
116### Rollbacks
117Roll back via Crontech's release history. Database migrations are additive; rolling back the service does not roll back the schema. If a migration needs reverting, write a new forward-migration.
118
119### Backups
120Neon handles PITR + branch snapshots — configure retention in the Neon console. The bare repos on the persistent volume must be backed up separately (filesystem snapshot of `/data/repos`). See BUILD_BIBLE §2.6 for what still needs wiring on the observability side.
121
122---
8ade77bClaude123
988380aClaude124## 7. Graceful-degradation matrix
8ade77bClaude125
988380aClaude126| Missing secret | Effect |
127|---|---|
128| `DATABASE_URL` | App boots, `/healthz` returns 200, any DB-backed route returns 500. Do not deploy without it. |
129| `ANTHROPIC_API_KEY` | AI review, chat, triage, incident, completions, explain, test-gen, merge resolver all return deterministic fallback strings. Site remains fully usable as a plain git host. |
130| `GATETEST_API_KEY` | Outbound GateTest integration silently skipped. Local secret scanner + gate runner still execute. |
131| `RESEND_API_KEY` (with `EMAIL_PROVIDER=resend`) | `sendEmail()` still never throws; emails are logged instead of delivered. |
132| `VOYAGE_API_KEY` | Semantic search falls back to the deterministic hashing embedder. Quality drops; feature still works. |
8ade77bClaude133
988380aClaude134Every missing integration follows the same rule: **never break the primary request path**. See BUILD_BIBLE §4.9 for the full invariant list.