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.
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
- server UUID:
- Project:
tools(UUIDu8wooo0wwk4k8wcw48ww8oo8) - Source: Forgejo,
https://forgejo-rko8sk40400wscowk4scko0w.altweb.me/spencer/raycer.git, branchmain(mirrored to GitLabspencerflagg/raycer) - Compose file:
/docker-compose.coolify.yaml(the local-devdocker-compose.ymlis forraycer.testonly 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.