Running on Coolify
How to run Spine on a server managed by Coolify. The plain-Docker route uses install.sh; on Coolify, Coolify does that job.
How it fits: Coolify’s proxy already holds ports 80 and 443 and gets the HTTPS certificate. Spine’s own Caddy runs behind it on port 80, with no published ports, and still does Spine’s routing: /api/auth/* and the rest of /api/* to the API, and everything else to the web app. The Compose file for this is docker/coolify/docker-compose.yml, and each release also attaches it as docker-compose.coolify.yml.
Coolify’s screens change between versions. If a name below doesn’t match yours, the idea is the same: a Docker Compose resource, a domain on the caddy service, and its environment variables.
Before you start
Section titled “Before you start”-
A Coolify server (v4) whose proxy is running, and a DNS
Arecord forspine.example.compointing at it. -
While the repo is private, the server must be able to pull Spine’s images. On the server, as the user that runs Docker (usually root):
Terminal window echo <GitHub token with read:packages> | docker login ghcr.io -u <your GitHub user> --password-stdin
Deploy
Section titled “Deploy”- In Coolify, open a project and add a resource: Docker Compose Empty. Paste the contents of
docker-compose.coolify.yml, from the release you’re installing, then save. - The domain: on the
caddyservice, set the domain tohttps://spine.example.com. Coolify routes it to Caddy’s port 80, because the service declaresSERVICE_URL_CADDY_80. Leave the other services without a domain. - Environment variables: Coolify lists every setting in the file.
SERVICE_PASSWORD_64_AUTH(theAUTH_SECRET) andSERVICE_PASSWORD_POSTGRESare generated once and kept. CopySERVICE_PASSWORD_64_AUTHsomewhere safe: a restore needs the same one.- Fill in the email service (
SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASSWORD,SMTP_FROM) andBACKUP_PASSPHRASE. - For backups off the server, fill in
BACKUP_REMOTEand its keys (see Backups below). - Leave
SPINE_VERSIONempty to run the file’s own version.
- Deploy. The API applies its database migrations on the first start, then Coolify shows every service as healthy.
- Open
https://spine.example.comand create your account: you’re the Owner, and Spine is invite-only from then on. Then invite your team from Team › Users.
Behind Cloudflare Access
Section titled “Behind Cloudflare Access”If you put the domain behind Cloudflare Access (Zero Trust), for an extra sign-in before Spine’s own, let Spine’s own paths through. Otherwise the phone app, AI agents and customers’ quote links get Access’s sign-in page, which they can’t use.
- Add another self-hosted Access application for the same hostname, with these paths:
api/*: the API, which checks a Spine session on every request;.well-known/*: how AI agents find out how to sign in;quote/*: customers’ quote links.
- Give it a policy with action Bypass, including Everyone.
Cloudflare applies the more specific paths first, so everything else stays behind Access. To check from outside: curl https://<address>/api/health answers with Spine’s version, while the web pages still go to Access’s sign-in.
Backups
Section titled “Backups”On Coolify, backups go straight to an S3-compatible bucket (Cloudflare R2, Backblaze B2 or AWS S3), encrypted. Nothing is kept on the server, which Coolify can rebuild.
-
Make a bucket and a key that can read and write it. In the environment variables, set:
Terminal window BACKUP_REMOTE=s3://<bucket>/spine-betaBACKUP_REMOTE_ENDPOINT=https://<account>.r2.cloudflarestorage.com # not needed for AWSBACKUP_REMOTE_ACCESS_KEY=…BACKUP_REMOTE_SECRET_KEY=…BACKUP_PASSPHRASE=… # keep it somewhere other than the serverRedeploy so the API has them.
-
Add a Scheduled Task to the resource:
- command:
node apps/api/dist/scripts/backup.js --remote-only - container:
api - frequency:
0 2 * * *(02:00 every night)
Each run backs up the database and every file, encrypts the archive, uploads it, and keeps 7 daily and 4 weekly copies in the bucket.
- command:
-
Run the task once by hand and check its log ends with “Backup saved: s3://…”. The Owners’ “last backup” banner clears.
Upgrading
Section titled “Upgrading”- Run the backup task once by hand.
- Paste the new release’s
docker-compose.coolify.ymlover the old one, or just setSPINE_VERSIONto the new version (e.g.1.0.0-beta.2). - Redeploy. If the new version brings migrations, the API takes a database snapshot first (in the
spine_snapshotsvolume), then applies them; a failed migration changes nothing.
Settings › About Spine shows the new version.
Restoring
Section titled “Restoring”To restore a backup into a fresh Coolify deployment, or onto a plain server:
-
Deploy a fresh resource as above, with the old deployment’s
AUTH_SECRET. SetSERVICE_PASSWORD_64_AUTHto the old value before the first deploy. -
Download the backup from the bucket to the server.
-
On the server, find the resource’s folder:
docker compose lslists each project with its config file. In that folder (it holds the resource’sdocker-compose.ymland.env), putrestore.shfrom the release and run:Terminal window BACKUP_PASSPHRASE=… ./restore.sh /path/to/spine-backup-<date>.tar.gz.encIt stops the API and web app, checks everything, puts the database and files back, and starts them again.
Other platforms
Section titled “Other platforms”The same approach works on any platform whose proxy handles HTTPS in front of a Compose stack, such as Dokploy, CapRover or Portainer behind Traefik or nginx:
- Use the main
docker-compose.yml. Remove thecaddyservice’sports:, and setSITE_ADDRESS=:80andTRUSTED_PROXIES=private_ranges. - Point the platform’s domain at the
caddyservice, port 80. - Set
APP_URLto the public address, andAPI_URLto it plus/api.