raycer/deploy/COOLIFY.md
Spencer Flagg ed3a0d3ea3 Initial commit: raycer accountability PWA
Vanilla HTML/JS/CSS PWA with Dexie offline-first sync, Hono+SQLite backend,
served via nginx reverse-proxy. Two seed goals (no-sugar, no-social-media)
for users ray and cer.

Local dev runs at https://raycer.test via the shared Traefik proxy.
Production deploys to https://raycer.altweb.me on cool2026/personal via
docker-compose.coolify.yaml — see deploy/COOLIFY.md.
2026-04-23 16:45:06 +02:00

131 lines
4.6 KiB
Markdown

# Deploying raycer to Coolify
raycer runs in production at **https://raycer.altweb.me** as a Docker
Compose application managed by Coolify v4 on the `cool2026` instance.
## Where things live
- **Coolify dashboard:** https://cool2026.altweb.me
- **Server:** `personal` (Linode 91853095, Amsterdam, 2 GB)
- server UUID: `locg048kwko4sws8wcggc0o4`
- public IP: `172.235.183.140`
- **Project:** `tools` (UUID `u8wooo0wwk4k8wcw48ww8oo8`)
- **Source:** Forgejo, `https://forgejo-rko8sk40400wscowk4scko0w.altweb.me/spencer/raycer.git`, branch `main` (mirrored to GitLab `spencerflagg/raycer`)
- **Compose file:** `/docker-compose.coolify.yaml` (the local-dev `docker-compose.yml` is for `raycer.test` only and is not used in prod)
## Containers
| Service | Image source | Internal port | Public | Memory limit |
|----------|-----------------------------|---------------|-------------|--------------|
| backend | built from `./backend` | 3000 | (internal) | 256 MiB |
| frontend | built from `./frontend` | 80 | raycer.altweb.me | 64 MiB |
The frontend's nginx reverse-proxies `/api/*` to `http://backend:3000`.
## Required env (set in Coolify dashboard)
| Variable | Value | Notes |
|-----------------------------|------------------------------------|-------|
| `SERVICE_FQDN_FRONTEND_80` | `https://raycer.altweb.me` | Coolify magic var; injects Traefik labels for the frontend service. |
The backend's `NODE_ENV`, `PORT`, and `DB_PATH` are set inside the compose file.
## Persistent storage
A named Docker volume `raycer-data` is mounted into the backend at
`/data` and holds `raycer.sqlite`. Coolify creates and manages this
volume; it survives redeploys.
To inspect or back up:
```bash
ssh root@172.235.183.140
docker volume inspect <coolify-prefixed-name> # find via: docker volume ls | grep raycer
docker run --rm -v <volume>:/data -v $(pwd):/backup alpine \
tar czf /backup/raycer-sqlite-$(date +%F).tgz -C /data .
```
## Healthchecks
Both containers have a `HEALTHCHECK` (backend hits `/api/health`,
frontend hits `/`). Coolify's "deployment healthy" gate uses these.
## DNS
A record `raycer.altweb.me -> 172.235.183.140` (Linode domain ID
`1544692`, TTL 300). To recreate:
```bash
linode-cli domains records-create 1544692 \
--type A --name raycer --target 172.235.183.140 --ttl_sec 300
```
## How the app was created
The Coolify v4 dashboard does not have a great "create from existing
config" path, so the original create call hit the API directly:
```bash
TOKEN=$(jq -r '.instances[] | select(.name=="cool2026") | .token' \
~/.config/coolify/config.json)
curl -X POST https://cool2026.altweb.me/api/v1/applications/public \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_uuid": "u8wooo0wwk4k8wcw48ww8oo8",
"server_uuid": "locg048kwko4sws8wcggc0o4",
"environment_name": "production",
"git_repository": "https://forgejo-rko8sk40400wscowk4scko0w.altweb.me/spencer/raycer.git",
"git_branch": "main",
"build_pack": "dockercompose",
"docker_compose_location": "/docker-compose.coolify.yaml",
"name": "raycer",
"instant_deploy": false
}'
```
After creation, the FQDN env var was set:
```bash
curl -X POST https://cool2026.altweb.me/api/v1/applications/<APP_UUID>/envs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"SERVICE_FQDN_FRONTEND_80","value":"https://raycer.altweb.me","is_preview":false}'
```
Then deployed:
```bash
curl -X POST "https://cool2026.altweb.me/api/v1/deploy?uuid=<APP_UUID>&force=false" \
-H "Authorization: Bearer $TOKEN"
```
## Re-deploy
Pushing to `main` on Forgejo triggers an automatic redeploy via the
Coolify webhook (configured at app create time). To force a manual
redeploy:
```bash
coolify deploy <APP_UUID> --context cool2026
# or
curl -X POST "https://cool2026.altweb.me/api/v1/deploy?uuid=<APP_UUID>" \
-H "Authorization: Bearer $TOKEN"
```
## Verification
```bash
curl https://raycer.altweb.me/api/health # {"ok":true,...}
curl https://raycer.altweb.me/api/goals # both seeded goals
coolify app list --context cool2026 --format json | jq '.[] | select(.name=="raycer")'
```
## Resource notes
The `personal` server is 2 GB and tightly accounted for; `mem_limit`
values in [docker-compose.coolify.yaml](../docker-compose.coolify.yaml)
must be respected. Backend's `better-sqlite3` requires a brief native
compile during the image build (~10 s, ~100 MB peak); first deploy may
take a minute longer than subsequent ones.