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

4.6 KiB

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:

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:

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:

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:

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:

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:

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

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