151 lines
5.3 KiB
Markdown
151 lines
5.3 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`)
|
|
- **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 <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 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 <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.
|