Files
beenvoice/apps/web/docs/COOLIFY.md
T

8.2 KiB
Raw Blame History

Coolify deployment — beenvoice + Garage

beenvoice stores receipt files in S3-compatible storage when S3_BUCKET, S3_ACCESS_KEY, and S3_SECRET_KEY are set. Garage is the default on self-hosted Coolify (~50100 MB RAM vs MinIO's ~500 MB+).

Why getaddrinfo ENOTFOUND garage happens

Docker DNS resolves service names only inside the same Docker network.

Setup Does http://garage:<GARAGE_API_PORT> work?
Single Compose stack (app + garage together) Yes — Compose service name garage
beenvoice Application + Garage separate Compose No — each resource has its own network by default
Application + Garage with shared destination network + correct hostname Yes — hostname is usually garage-<resource-uuid>, not bare garage
Application + Garage via public domain (SERVICE_URL_GARAGE) Yes — no Docker DNS needed

Setting S3_ENDPOINT=http://garage:<GARAGE_API_PORT> on a standalone beenvoice Application fails because the app container is not on the Garage stack's network. Node returns ENOTFOUND garage.

Also avoid http://localhost:<GARAGE_API_PORT> inside the app container — that points at the app itself, not Garage.


Quick fix — keep beenvoice as Application + separate Garage compose

Use this if you are not migrating to a single Compose stack today.

This is the most reliable fix when beenvoice is a Coolify Application (Dockerfile) and Garage is a separate Compose resource.

  1. Update the Garage stack to the latest docker-compose.coolify-garage.yml from this repo (includes SERVICE_FQDN_GARAGE) and redeploy the Garage resource.
  2. In the Garage Compose resource → assign a domain to GARAGE_API_PORT (default 3900, e.g. s3.yourdomain.com). Coolify generates TLS via Traefik/Caddy.
  3. Open the Garage resource Environment tab and copy SERVICE_URL_GARAGE (e.g. https://s3.yourdomain.com).
  4. On the beenvoice Application → Environment:
S3_ENDPOINT=https://s3.yourdomain.com
S3_BUCKET=beenvoice-receipts
S3_ACCESS_KEY=<same as GARAGE_DEFAULT_ACCESS_KEY / S3_ACCESS_KEY on Garage stack>
S3_SECRET_KEY=<same as GARAGE_DEFAULT_SECRET_KEY / S3_SECRET_KEY on Garage stack>
S3_REGION=garage
  1. Redeploy beenvoice (restart is not enough after env changes on some Coolify versions — trigger a full redeploy).

S3_FORCE_PATH_STYLE defaults to on when S3_ENDPOINT is set (required for Garage behind a reverse proxy). Only set S3_FORCE_PATH_STYLE=false if you use AWS S3 with virtual-hosted-style buckets.

Path B — internal Docker DNS (same destination, no public Garage domain)

Use when you want S3 API traffic to stay on the Docker network.

  1. Put beenvoice Application and Garage Compose in the same Coolify project and same destination (server/network).
  2. Garage Compose resourceAdvanced → enable Connect to Predefined Networkredeploy Garage.
  3. beenvoice ApplicationAdvanced → enable Connect to Predefined Network (same destination) → redeploy beenvoice.
  4. Find the Garage resource UUID (in the Coolify URL, e.g. .../service/abc123def456, or env COOLIFY_RESOURCE_UUID on the Garage container).
  5. Set on beenvoice Application:
S3_ENDPOINT=http://garage-<GARAGE_RESOURCE_UUID>:<GARAGE_API_PORT>

Example with the default port and resource UUID k8w2o0g4s0g8: S3_ENDPOINT=http://garage-k8w2o0g4s0g8:3900.

Do not use bare garage unless you verified it resolves from inside the beenvoice container (recent Coolify versions may also register the short service name when both sides use Connect to Predefined Network — if wget http://garage:<GARAGE_API_PORT> fails, use the garage-<uuid> form or Path A).

  1. Match credentials and bucket:
S3_BUCKET=beenvoice-receipts
S3_ACCESS_KEY=<S3_ACCESS_KEY on Garage stack>
S3_SECRET_KEY=<S3_SECRET_KEY on Garage stack>
S3_REGION=garage

Deploy the root docker-compose.coolify.yml as one Coolify Docker Compose resource (app + Postgres + Garage). This is the lowest-friction production layout on Coolify.

  1. Coolify → New ResourceDocker Compose
  2. Point at this repo; compose file: docker-compose.coolify.yml
  3. Set env vars from .env.example: AUTH_SECRET, POSTGRES_PASSWORD, S3_ACCESS_KEY, S3_SECRET_KEY, and a 64-character hexadecimal GARAGE_RPC_SECRET (openssl rand -hex 32). Garage has a valid development fallback, but production should override it.
  4. Assign a domain to the app service (Coolify fills SERVICE_URL_APP / BETTER_AUTH_URL automatically).
  5. Optionally set APP_PORT, POSTGRES_PORT, GARAGE_API_PORT, GARAGE_RPC_PORT, GARAGE_WEB_PORT, or GARAGE_ADMIN_PORT. Defaults are 3000, 5432, and 39003903 respectively.
  6. Only the app port is exposed to Coolify's proxy. PostgreSQL and every Garage listener remain reachable only through the stack's internal Docker network.
  7. Do not override S3_ENDPOINT — the compose file derives it from GARAGE_API_PORT on the shared network.
  8. Redeploy.

Alternative: docker-compose.yml works the same way; docker-compose.coolify.yml adds Coolify magic vars (SERVICE_FQDN_APP) and omits host port bindings for db/Garage.

Migrating from Application + external Postgres + Garage (or legacy MinIO)

Current Action
beenvoice Application Remove after Compose stack is live
Separate Postgres Dump/restore into stack db, or keep external DB and delete the db service from the compose file
Garage / MinIO compose Remove after data migrated (rclone) or re-point receipts (new bucket)
Env vars Move AUTH_SECRET, Resend, Authentik, etc. to the Compose resource env

Migrating from MinIO: Garage defaults to port 3900 (not 9000) and uses Garage-format access keys (GK…). Update S3_ENDPOINT, S3_REGION=garage, and credentials. Receipt blobs in the old MinIO volume are not auto-migrated.


Compose file reference

File Purpose
docker-compose.coolify.yml Recommended — full stack for one Coolify Compose resource
docker-compose.yml Full stack (local/VPS); also valid on Coolify
docker-compose.coolify-garage.yml Garage only; pair with beenvoice Application (Path A or B above)

Do not add networks: coolify: external: true unless you know the exact external network name on your server. Coolify v4 uses destinations; network names are often UUID-based. Prefer the UI Connect to Predefined Network toggle over hard-coding coolify in compose.


Checklist (Application + separate Garage)

  • Garage stack redeployed with current docker-compose.coolify-garage.yml
  • Path A: domain targets GARAGE_API_PORT + S3_ENDPOINT = SERVICE_URL_GARAGE; or Path B: Connect to Predefined Network on both resources + S3_ENDPOINT=http://garage-<uuid>:<GARAGE_API_PORT>
  • S3_ENDPOINT is not a bare http://garage:<port> across separate resources and is not localhost
  • S3_ACCESS_KEY / S3_SECRET_KEY match the Garage stack env
  • S3_BUCKET exists (Garage --default-bucket creates beenvoice-receipts on first start)
  • Redeployed beenvoice after env or network changes

Verify from the beenvoice container

# Shell into beenvoice app container on the Coolify server
docker exec -it <beenvoice-container> sh

# Path A — public URL (403/404 on root is fine — confirms DNS + TLS)
wget -qO- "https://s3.yourdomain.com" || curl -sf "https://s3.yourdomain.com"

# Path B — internal host from S3_ENDPOINT
wget -qO- "http://garage-<uuid>:<GARAGE_API_PORT>" || curl -sf "http://garage-<uuid>:<GARAGE_API_PORT>"

If this fails with "bad address" or timeout, fix networking / S3_ENDPOINT before debugging app code. On first S3 use, the app logs a hint if DNS fails or if S3_ENDPOINT still uses bare garage in production.