# 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`) - **Application UUID:** `zsookwggwcss08wkkc4gookg` - **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 + per-service domain The frontend's public domain is set on the application record itself (not as a normal env var) via the `docker_compose_domains` field. Coolify reads this and generates the Traefik labels for the `frontend` service: ```json { "frontend": { "name": "frontend", "domain": "https://raycer.altweb.me" } } ``` Coolify also auto-fills these env vars for the running containers (no manual entry required): | Variable | Value | Source | |-----------------------------|------------------------------------|--------| | `SERVICE_FQDN_FRONTEND` | `https://raycer.altweb.me` | derived from `docker_compose_domains.frontend.domain` | | `SERVICE_FQDN_FRONTEND_80` | `https://raycer.altweb.me` | declared in compose, overridden to literal value | 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 # find via: docker volume ls | grep raycer docker run --rm -v :/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 per-service domain was bound (this is the part that makes Coolify emit Traefik labels for the frontend service and request a Let's Encrypt cert for `raycer.altweb.me`): ```bash APP_UUID=zsookwggwcss08wkkc4gookg curl -X PATCH https://cool2026.altweb.me/api/v1/applications/$APP_UUID \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "docker_compose_domains": { "frontend": { "name": "frontend", "domain": "https://raycer.altweb.me" } } }' ``` Then deployed: ```bash curl -X POST "https://cool2026.altweb.me/api/v1/deploy?uuid=$APP_UUID&force=true" \ -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 --context cool2026 # or curl -X POST "https://cool2026.altweb.me/api/v1/deploy?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.