Commit5843260
feat(deploy): one-command metal-box deploy via Caddy + compose
feat(deploy): one-command metal-box deploy via Caddy + compose Adds Caddyfile bundled with docker-compose.yml so `docker compose up -d` on the metal box at 45.76.171.37 gives gluecron.com + www.gluecron.com TLS for free via Let's Encrypt. DNS already resolves (Cloudflare "DNS only"), so Caddy's HTTP-01 challenge just works. Intended as the interim path until we flip DNS to Fly.io; everything here is disposable once Crontech absorbs hosting.
3 files changed+141−5584326092522f8cc4673145fa1cde0be1f60878d
3 changed files+141−5
AddedCaddyfile+26−0View fileUnifiedSplit
@@ -0,0 +1,26 @@
1# Caddy reverse proxy for the metal-box deploy (45.76.171.37).
2# Automatic HTTPS via Let's Encrypt — DNS already points here so HTTP-01 works.
3# Swap ACME_EMAIL when a real ops mailbox exists.
4
5{
6 email ops@gluecron.com
7}
8
9gluecron.com, www.gluecron.com {
10 encode gzip zstd
11 reverse_proxy gluecron:3000
12
13 log {
14 output file /var/log/caddy/gluecron.log {
15 roll_size 50mb
16 roll_keep 7
17 }
18 format json
19 }
20
21 header {
22 Strict-Transport-Security "max-age=31536000; includeSubDomains"
23 X-Content-Type-Options nosniff
24 Referrer-Policy strict-origin-when-cross-origin
25 }
26}
AddedDEPLOY_METAL.md+81−0View fileUnifiedSplit
@@ -0,0 +1,81 @@
1# DEPLOY_METAL.md — metal-box deploy (interim)
2
3This is the shortest path to gluecron.com being live on the existing Vultr
4box at `45.76.171.37`. It's a stop-gap — everything migrates onto Crontech
5once Crontech is absorbing hosting, and DNS gets flipped (to Fly.io briefly
6or straight to Crontech) at that point. Don't polish this; throw it away
7later.
8
9## What's already in place
10
11- DNS: `gluecron.com` and `www.gluecron.com` A records point at
12 `45.76.171.37`. Proxy status is "DNS only" in Cloudflare — keep it that
13 way for the cert handshake.
14- `Dockerfile`, `docker-compose.yml`, and `Caddyfile` are all in the repo.
15 `docker compose up -d` brings up both the app and Caddy (auto-HTTPS via
16 Let's Encrypt).
17- App exposes `/healthz`, `/readyz`, `/status` per BUILD_BIBLE §2.6.
18
19## One-time box setup
20
211. SSH in: `ssh root@45.76.171.37`
222. Install Docker (skip if already installed):
23 ```sh
24 curl -fsSL https://get.docker.com | sh
25 ```
263. Clone the repo:
27 ```sh
28 git clone https://github.com/ccantynz-alt/Gluecron.com.git /opt/gluecron
29 cd /opt/gluecron
30 ```
314. Create `.env` (copy from `.env.example` and fill the real values — at
32 minimum `DATABASE_URL` pointing at Neon):
33 ```sh
34 cp .env.example .env
35 vim .env
36 ```
37
38## Deploy
39
40```sh
41cd /opt/gluecron
42git pull
43docker compose up -d --build
44```
45
46First run: Caddy requests a Let's Encrypt cert. Watch:
47```sh
48docker compose logs -f caddy
49```
50
51Once you see `certificate obtained successfully`, hit:
52- `https://gluecron.com/healthz` → `ok`
53- `https://gluecron.com/readyz` → `ok`
54- `https://gluecron.com/status` → status page
55
56## First-time DB migration
57
58After the container is up:
59```sh
60docker compose exec gluecron bun run db:migrate
61```
62
63## Later redeploys
64
65```sh
66cd /opt/gluecron && git pull && docker compose up -d --build
67```
68
69## Rollback
70
71```sh
72cd /opt/gluecron && git checkout <prev-sha> && docker compose up -d --build
73```
74
75## When Crontech absorbs this
76
771. Point DNS at the Crontech edge (Caddy on the Crontech box, or whatever
78 Crontech routes through).
792. Push gluecron.com via git to Crontech — BLK-009 deploy pipeline builds
80 and serves it.
813. Tear down this box. Nothing here is worth preserving.
Modifieddocker-compose.yml+34−5View fileUnifiedSplit
@@ -1,23 +1,52 @@
11services:
22 gluecron:
33 build: .
4 ports:
5 - "3000:3000"
4 expose:
5 - "3000"
66 environment:
77 - DATABASE_URL=${DATABASE_URL}
88 - GIT_REPOS_PATH=/data/repos
99 - PORT=3000
1010 - NODE_ENV=production
11 - GATETEST_URL=https://gatetest.ai/api/events/push
12 - CRONTECH_DEPLOY_URL=https://crontech.ai/api/trpc/tenant.deploy
11 - GATETEST_URL=${GATETEST_URL:-https://gatetest.ai/api/events/push}
12 - CRONTECH_DEPLOY_URL=${CRONTECH_DEPLOY_URL:-https://crontech.ai/api/trpc/tenant.deploy}
13 - APP_BASE_URL=${APP_BASE_URL:-https://gluecron.com}
14 - GATETEST_API_KEY=${GATETEST_API_KEY}
15 - GATETEST_CALLBACK_SECRET=${GATETEST_CALLBACK_SECRET}
16 - GATETEST_HMAC_SECRET=${GATETEST_HMAC_SECRET}
17 - GLUECRON_WEBHOOK_SECRET=${GLUECRON_WEBHOOK_SECRET}
18 - CRONTECH_EVENT_TOKEN=${CRONTECH_EVENT_TOKEN}
19 - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
20 - EMAIL_PROVIDER=${EMAIL_PROVIDER:-log}
21 - EMAIL_FROM=${EMAIL_FROM}
22 - RESEND_API_KEY=${RESEND_API_KEY}
23 - ERROR_WEBHOOK_URL=${ERROR_WEBHOOK_URL}
24 - SENTRY_DSN=${SENTRY_DSN}
1325 volumes:
1426 - git-repos:/data/repos
1527 restart: unless-stopped
1628 healthcheck:
17 test: ["CMD", "curl", "-f", "http://localhost:3000/"]
29 test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
1830 interval: 30s
1931 timeout: 10s
2032 retries: 3
2133
34 caddy:
35 image: caddy:2-alpine
36 restart: unless-stopped
37 ports:
38 - "80:80"
39 - "443:443"
40 volumes:
41 - ./Caddyfile:/etc/caddy/Caddyfile:ro
42 - caddy-data:/data
43 - caddy-config:/config
44 - caddy-logs:/var/log/caddy
45 depends_on:
46 - gluecron
47
2248volumes:
2349 git-repos:
50 caddy-data:
51 caddy-config:
52 caddy-logs:
2453