Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

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

DEPLOYMENT_RUNBOOK.mdBlame283 lines · 3 contributors
7703650Claude1# Gluecron Deployment Runbook
2
3Step-by-step guide for taking Gluecron from zero to running at
4`gluecron.com`. This runbook targets **infrastructure deployment** —
5Gluecron is deployed as free internal infrastructure for Craig's
6ecosystem, **not** as a paid product.
7
9dd96b9Test User8> **Day-to-day operations** (enable AI auto-merge, trigger a deploy,
9> rollback) live at [`/admin/ops`](https://gluecron.com/admin/ops) once
10> the box is up. Deploy progress streams to
11> [`/admin/deploys`](https://gluecron.com/admin/deploys). This runbook
12> covers **first-time bootstrap only** — terminal steps below are
13> appropriate because there is no service yet to click against.
14
7703650Claude15---
16
17## What Gluecron is today
18
19- Git hosting (clone / push / fetch over Smart HTTP)
20- Web UI: issues, PRs, code browser, webhooks, SSH keys, OAuth, 2FA
21- Integrates with **GateTest** (scans on push) and **Crontech**
22 (deploys on push to `main`)
23- **Not a paid product.** No Stripe. No tiers. Free infrastructure
24 for Craig's ecosystem.
25- Pre-launch banner visible; public signups gated until owner review.
26
27---
28
29## Prerequisites
30
31- **Neon** account (managed Postgres) — <https://neon.tech>
32- **Fly.io** *or* **Railway** account (container deployment)
33- Domain `gluecron.com` in hand (registrar access for DNS records)
34- *(Optional)* **Resend** account for transactional email
35- *(Optional)* **Anthropic** API key for AI features (copilot,
36 changelog summarisation, semantic search)
37- *(Optional)* **Voyage** API key for semantic code search embeddings
38- Local tooling: `bun`, `git`, `flyctl` or `railway` CLI
39
40---
41
42## Phase 1 — Provision Postgres (Neon)
43
441. Sign in to <https://neon.tech> and create a project named
45 `gluecron`.
462. From the project dashboard, copy the pooled connection string.
47 It looks like:
7a03066Dictation App48 `postgresql://user:REDACTED@ep-xxx.eu-west-2.aws.neon.tech/neondb?sslmode=require`
7703650Claude493. Save this value — it becomes `DATABASE_URL` in Phase 3.
504. **Do NOT run migrations yet.** The container runs them as its
51 release step (see `fly.toml` → `[deploy] release_command`).
52
53---
54
55## Phase 2 — Choose a platform
56
57Both platforms ship from the checked-in `Dockerfile`. Pick one.
58
59### Option A — Fly.io (recommended; `fly.toml` already exists)
60
61`fly.toml` already configures the persistent `gluecron_repos`
62volume mounted at `/app/repos` and wires up migrations as the
63release command.
64
65```bash
66fly auth login
67fly launch --no-deploy # accept existing fly.toml
68fly volumes create gluecron_repos --size 10 --region lhr
69fly secrets set \
b105d39Dictation App70 DATABASE_URL="<DATABASE_URL_FROM_NEON>" \
7703650Claude71 APP_BASE_URL="https://gluecron.com" \
72 WEBAUTHN_RP_ID="gluecron.com" \
73 WEBAUTHN_ORIGIN="https://gluecron.com" \
74 WEBAUTHN_RP_NAME="gluecron" \
75 GATETEST_URL="https://gatetest.io/api/scan/run" \
76 GATETEST_API_KEY="..." \
77 GATETEST_CALLBACK_SECRET="..." \
78 GATETEST_HMAC_SECRET="..." \
79 CRONTECH_DEPLOY_URL="https://crontech.ai/api/hooks/gluecron/push" \
80 GLUECRON_WEBHOOK_SECRET="..." \
81 CRONTECH_EVENT_TOKEN="..." \
82 ANTHROPIC_API_KEY="..." \
83 EMAIL_PROVIDER="resend" \
84 RESEND_API_KEY="..." \
85 EMAIL_FROM="gluecron <no-reply@gluecron.com>"
86fly deploy
87```
88
89### Option B — Railway (`railway.toml` already exists)
90
911. <https://railway.app> → new project → **Deploy from GitHub**.
922. Select the Gluecron repo; Railway picks up the `Dockerfile`
93 via `railway.toml`.
943. Add a persistent volume mounted at `/app/repos` (Railway UI
95 → service → Volumes).
964. Set all environment variables from Phase 3 in the Railway
97 dashboard.
985. Railway will run `bun run db:migrate` as the release command
99 on deploy.
100
101---
102
103## Phase 3 — Environment variables
104
105All variables that Gluecron reads from `process.env`, enumerated
106from `src/lib/config.ts` and direct `process.env.*` references in
107`src/`.
108
109### Core runtime
110
111| Variable | Required? | Example | Notes |
112|---|---|---|---|
b105d39Dictation App113| `DATABASE_URL` | **Yes** | `<DATABASE_URL_FROM_NEON>` | Neon pooled connection string. |
7703650Claude114| `PORT` | No | `3000` | Preset in `fly.toml` / `railway.toml`. |
115| `GIT_REPOS_PATH` | **Yes** in prod | `/app/repos` | Must be a persistent volume — this is where bare repos live. |
116| `NODE_ENV` | **Yes** in prod | `production` | Enables secure cookies and rate limiting. Preset in both configs. |
117| `APP_BASE_URL` | **Yes** | `https://gluecron.com` | Canonical base URL; used in outbound webhooks and email links. No trailing slash. |
118
119### WebAuthn (passkeys / 2FA)
120
121| Variable | Required? | Example | Notes |
122|---|---|---|---|
123| `WEBAUTHN_RP_ID` | **Yes** | `gluecron.com` | Domain only, **no scheme, no port**. Passkeys are bound to this value; changing it invalidates existing keys. |
124| `WEBAUTHN_ORIGIN` | **Yes** | `https://gluecron.com` | Full origin including scheme. Must match what the browser sees. |
125| `WEBAUTHN_RP_NAME` | No | `gluecron` | Human-facing name shown by the browser. Default `gluecron`. |
126
127### GateTest integration (scans on push)
128
129| Variable | Required? | Example | Notes |
130|---|---|---|---|
a4e1564Claude131| `GATETEST_URL` | **Yes** (for integration) | `https://gatetest.ai/api/events/push` | Default `https://gatetest.ai/api/events/push`. |
7703650Claude132| `GATETEST_API_KEY` | **Yes** | `gtk_...` | Outbound bearer token sent to GateTest. |
133| `GATETEST_CALLBACK_SECRET` | **Yes** | *(random 32-byte hex)* | Bearer token GateTest uses when posting scan results back to Gluecron (`/hooks/gatetest`). |
134| `GATETEST_HMAC_SECRET` | **Yes** | *(random 32-byte hex)* | HMAC signing secret for GateTest → Gluecron callbacks. |
135
136### Crontech integration (deploys on push to main)
137
138| Variable | Required? | Example | Notes |
139|---|---|---|---|
140| `CRONTECH_DEPLOY_URL` | **Yes** (for integration) | `https://crontech.ai/api/hooks/gluecron/push` | Outbound webhook target. |
141| `GLUECRON_WEBHOOK_SECRET` | **Yes** | *(random 32-byte hex)* | Bearer token on outbound Crontech deploy webhook. Empty → Crontech returns 401 and deploys fail. |
142| `CRONTECH_EVENT_TOKEN` | **Yes** | *(random 32-byte hex)* | Bearer token Crontech uses when posting deploy events back to Gluecron (`/api/deploy-events`). |
143
144### Email (optional — `log` provider works without)
145
146| Variable | Required? | Example | Notes |
147|---|---|---|---|
148| `EMAIL_PROVIDER` | No | `resend` or `log` | Default `log` (writes to stderr). Set `resend` for real sending. |
149| `RESEND_API_KEY` | Only if `EMAIL_PROVIDER=resend` | `re_...` | Resend API key. |
150| `EMAIL_FROM` | No | `gluecron <no-reply@gluecron.com>` | From header. Default `gluecron <no-reply@gluecron.local>`. |
151
152### AI / semantic search (optional)
153
154| Variable | Required? | Example | Notes |
155|---|---|---|---|
156| `ANTHROPIC_API_KEY` | No | `sk-ant-...` | Enables copilot, PR review, changelog summarisation. |
157| `VOYAGE_API_KEY` | No | `pa-...` | Enables Voyage embeddings for semantic code search. Falls back to a local model when absent. |
158
159**Total: 18 required/useful env vars** (plus `NODE_ENV`, `PORT`,
160`VOYAGE_API_KEY` as optional tuning).
161
162---
163
164## Phase 4 — Run migrations
165
166On both Fly.io and Railway, `bun run db:migrate` is the release
167command and runs automatically before traffic is routed to a new
9dd96b9Test User168revision. Once the box is up, the easiest way to re-run migrations is
169to click **"Trigger a deploy"** on
170[`/admin/ops`](https://gluecron.com/admin/ops) — the release command
171fires `bun run db:migrate` as part of the redeploy.
172
173Verify the server starts cleanly — `bun run` should not error on
174boot.
175
176<details>
177<summary>Manual fallback (terminal)</summary>
178
179Only needed during first-time bootstrap before `/admin/ops` is
180reachable, or if `/admin/ops` itself is broken.
7703650Claude181
182```bash
183# Fly.io
184fly ssh console
185bun run db:migrate
186
187# Railway
188railway run bun run db:migrate
189```
190
9dd96b9Test User191</details>
7703650Claude192
193---
194
195## Phase 5 — DNS
196
1971. Point `gluecron.com` at your platform:
198 - **Fly.io**: `fly ips list` → create `A` and `AAAA` records
199 to the IPv4/IPv6 shown. Alternatively
200 `fly certs add gluecron.com` and follow the CNAME/ALIAS
201 instructions.
202 - **Railway**: add `gluecron.com` in the service → Settings →
203 Domains. Railway prints the target `CNAME`.
2042. Wait for Let's Encrypt to provision the certificate (usually
205 under 5 minutes). Verify:
206
207```bash
208curl -I https://gluecron.com/
209```
210
211---
212
213## Phase 6 — Smoke test
214
215- [ ] Visit <https://gluecron.com/> — pre-launch banner is visible.
216- [ ] Register an account via email / OAuth.
217- [ ] Log in, visit **Settings → SSH keys**, add an SSH public key.
218- [ ] Create a repository from the UI.
219- [ ] `git clone https://gluecron.com/<you>/<repo>.git` — must
220 succeed (this was the Smart HTTP bug fixed in commit
221 `676be75`).
222- [ ] `git push` — verify it triggers a GateTest scan (if
223 `GATETEST_URL` is set).
224- [ ] `git push` to `main` — verify it triggers a Crontech deploy
225 (if `CRONTECH_DEPLOY_URL` is set).
226
227---
228
229## Phase 7 — Integration wires with GateTest & Crontech
230
231The three apps share secrets pairwise. Each secret value **must be
232identical on both sides of the wire**.
233
234| Secret | Set on Gluecron | Set on the other side |
235|---|---|---|
236| `GATETEST_API_KEY` | outbound auth to GateTest | GateTest validates this on its `/api/scan/run` endpoint |
237| `GATETEST_CALLBACK_SECRET` | validates callbacks from GateTest | GateTest sends as bearer on scan-result POSTs |
238| `GATETEST_HMAC_SECRET` | verifies signatures on GateTest callbacks | GateTest signs outbound callbacks with this |
239| `GLUECRON_WEBHOOK_SECRET` | outbound auth to Crontech | Crontech validates this on `/api/hooks/gluecron/push` |
240| `CRONTECH_EVENT_TOKEN` | validates deploy-event POSTs from Crontech | Crontech sends as bearer on `/api/deploy-events` |
241
242**Rule of thumb:** generate each secret once with
243`openssl rand -hex 32`, then copy the same value into the matching
244variable on both services.
245
246---
247
248## Phase 8 — Pre-launch → launch
249
250When Craig is ready to open signups:
251
2521. Remove the pre-launch banner — revert the `.prelaunch-banner`
253 block in `src/views/layout.tsx` that was added in commit
254 `4a52a98`.
2552. Commit: `feat(launch): remove pre-launch banner`.
2563. Push — the container redeploys automatically.
257
258---
259
260## Known limitations at launch
261
262- **No legal pages** — see `docs/legal-audit.md`; needs an owner
263 decision on ToS / Privacy before broad public signups.
264- **No billing** — free forever, or bolt on Stripe later.
265- **18 remaining `tsc` errors in locked files** — they don't block
266 runtime; clean-up tracked separately.
267
268---
269
270## Troubleshooting
271
272| Symptom | Likely cause | Fix |
273|---|---|---|
274| `git clone` reports "empty repo" | The repo has no commits. | Push at least one commit first — an empty bare repo has no refs to advertise. |
275| Smart HTTP returns `404` on `info/refs` | `GIT_REPOS_PATH` is wrong or the volume isn't mounted with write access. | Check the volume mount in `fly.toml` / Railway; confirm the container user can read the bare repo dir. |
276| WebAuthn / passkey registration fails | `WEBAUTHN_RP_ID` doesn't match the domain, or includes a scheme/port. | Must be the bare host (`gluecron.com`); ensure `WEBAUTHN_ORIGIN` matches what the browser sees, including `https://`. |
277| GateTest callbacks rejected with `401` | `GATETEST_CALLBACK_SECRET` mismatch between Gluecron and GateTest. | Re-generate the secret and set the same value on both services. |
278| Crontech deploys fail with `401` | `GLUECRON_WEBHOOK_SECRET` is empty or mismatched. | Set to the same value on both services; the config default is empty, which Crontech rejects. |
279| Outbound emails don't arrive | `EMAIL_PROVIDER` defaults to `log`. | Set `EMAIL_PROVIDER=resend` and configure `RESEND_API_KEY` + `EMAIL_FROM`. |
280
281---
282
b105d39Dictation App283*Last reviewed: 2026-04-16.*