Refactor Coolify deployment from MinIO to Garage
- Updated README and documentation to reflect the transition from MinIO to Garage for S3-compatible storage. - Removed the MinIO-specific docker-compose files and replaced them with Garage configurations. - Adjusted environment variables and service dependencies in docker-compose files to accommodate Garage. - Modified application code to log hints for Garage instead of MinIO. - Ensured all references to MinIO in the codebase and documentation are replaced with Garage.
This commit is contained in:
+61
-59
@@ -1,100 +1,102 @@
|
||||
# Coolify deployment — Beevoice + MinIO
|
||||
# Coolify deployment — beenvoice + Garage
|
||||
|
||||
Beevoice stores receipt files in S3-compatible storage when `S3_BUCKET`, `S3_ACCESS_KEY`, and `S3_SECRET_KEY` are set. MinIO is the usual choice on self-hosted Coolify.
|
||||
beenvoice stores receipt files in S3-compatible storage when `S3_BUCKET`, `S3_ACCESS_KEY`, and `S3_SECRET_KEY` are set. [Garage](https://garagehq.deuxfleurs.fr/) is the default on self-hosted Coolify (~50–100 MB RAM vs MinIO's ~500 MB+).
|
||||
|
||||
## Why `getaddrinfo ENOTFOUND minio` happens
|
||||
## Why `getaddrinfo ENOTFOUND garage` happens
|
||||
|
||||
Docker DNS resolves service names **only inside the same Docker network**.
|
||||
|
||||
| Setup | Does `http://minio:9000` work? |
|
||||
|-------|-------------------------------|
|
||||
| Single Compose stack (app + minio together) | Yes — Compose service name `minio` |
|
||||
| Beevoice **Application** + MinIO **separate Compose** | **No** — each resource has its own network by default |
|
||||
| Application + MinIO with shared destination network + correct hostname | Yes — hostname is usually **`minio-<resource-uuid>`**, not bare `minio` |
|
||||
| Application + MinIO via **public domain** (`SERVICE_URL_MINIO_9000`) | Yes — no Docker DNS needed |
|
||||
| Setup | Does `http://garage:3900` 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_3900`) | Yes — no Docker DNS needed |
|
||||
|
||||
Setting `S3_ENDPOINT=http://minio:9000` on a standalone Beevoice Application fails because the app container is not on the MinIO stack's network. Node returns `ENOTFOUND minio`.
|
||||
Setting `S3_ENDPOINT=http://garage:3900` 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:9000` inside the app container — that points at the app itself, not MinIO.
|
||||
Also avoid `http://localhost:3900` inside the app container — that points at the app itself, not Garage.
|
||||
|
||||
---
|
||||
|
||||
## Quick fix — keep Beevoice as Application + separate MinIO compose
|
||||
## Quick fix — keep beenvoice as Application + separate Garage compose
|
||||
|
||||
Use this if you are **not** migrating to a single Compose stack today.
|
||||
|
||||
### Path A — public MinIO URL (recommended, works without shared Docker network)
|
||||
### Path A — public Garage URL (recommended, works without shared Docker network)
|
||||
|
||||
This is the most reliable fix when Beevoice is a Coolify **Application** (Dockerfile) and MinIO is a separate Compose resource.
|
||||
This is the most reliable fix when beenvoice is a Coolify **Application** (Dockerfile) and Garage is a separate Compose resource.
|
||||
|
||||
1. **Update the MinIO stack** to the latest `docker-compose.coolify-minio.yml` from this repo (includes `SERVICE_FQDN_MINIO_9000`) and **redeploy** the MinIO resource.
|
||||
2. In the **MinIO Compose resource** → assign a domain for **port 9000** (e.g. `s3.yourdomain.com`). Coolify generates TLS via Traefik/Caddy.
|
||||
3. Open the MinIO resource **Environment** tab and copy **`SERVICE_URL_MINIO_9000`** (e.g. `https://s3.yourdomain.com`).
|
||||
4. On the **Beevoice Application** → Environment:
|
||||
1. **Update the Garage stack** to the latest `docker-compose.coolify-garage.yml` from this repo (includes `SERVICE_FQDN_GARAGE_3900`) and **redeploy** the Garage resource.
|
||||
2. In the **Garage Compose resource** → assign a domain for **port 3900** (e.g. `s3.yourdomain.com`). Coolify generates TLS via Traefik/Caddy.
|
||||
3. Open the Garage resource **Environment** tab and copy **`SERVICE_URL_GARAGE_3900`** (e.g. `https://s3.yourdomain.com`).
|
||||
4. On the **beenvoice Application** → Environment:
|
||||
|
||||
```env
|
||||
S3_ENDPOINT=https://s3.yourdomain.com
|
||||
S3_BUCKET=beenvoice-receipts
|
||||
S3_ACCESS_KEY=<same as MINIO_ROOT_USER>
|
||||
S3_SECRET_KEY=<same as MINIO_ROOT_PASSWORD>
|
||||
S3_REGION=us-east-1
|
||||
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
|
||||
```
|
||||
|
||||
5. **Redeploy Beevoice** (restart is not enough after env changes on some Coolify versions — trigger a full redeploy).
|
||||
5. **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 MinIO behind a reverse proxy). Only set `S3_FORCE_PATH_STYLE=false` if you use AWS S3 with virtual-hosted-style buckets.
|
||||
`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 MinIO domain)
|
||||
### Path B — internal Docker DNS (same destination, no public Garage domain)
|
||||
|
||||
Use when you want MinIO API traffic to stay on the Docker network.
|
||||
Use when you want S3 API traffic to stay on the Docker network.
|
||||
|
||||
1. Put Beevoice Application and MinIO Compose in the **same Coolify project** and **same destination** (server/network).
|
||||
2. **MinIO Compose resource** → **Advanced** → enable **Connect to Predefined Network** → **redeploy MinIO**.
|
||||
3. **Beevoice Application** → **Advanced** → enable **Connect to Predefined Network** (same destination) → **redeploy Beevoice**.
|
||||
4. Find the MinIO resource **UUID** (in the Coolify URL, e.g. `.../service/abc123def456`, or env `COOLIFY_RESOURCE_UUID` on the MinIO container).
|
||||
5. Set on Beevoice Application:
|
||||
1. Put beenvoice Application and Garage Compose in the **same Coolify project** and **same destination** (server/network).
|
||||
2. **Garage Compose resource** → **Advanced** → enable **Connect to Predefined Network** → **redeploy Garage**.
|
||||
3. **beenvoice Application** → **Advanced** → 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:
|
||||
|
||||
```env
|
||||
S3_ENDPOINT=http://minio-<MINIO_RESOURCE_UUID>:9000
|
||||
S3_ENDPOINT=http://garage-<GARAGE_RESOURCE_UUID>:3900
|
||||
```
|
||||
|
||||
Example: resource UUID `k8w2o0g4s0g8` → `S3_ENDPOINT=http://minio-k8w2o0g4s0g8:9000`.
|
||||
Example: resource UUID `k8w2o0g4s0g8` → `S3_ENDPOINT=http://garage-k8w2o0g4s0g8:3900`.
|
||||
|
||||
**Do not use bare `minio`** unless you verified it resolves from inside the Beevoice container (recent Coolify versions may also register the short service name when both sides use Connect to Predefined Network — if `wget http://minio:9000/minio/health/live` fails, use the `minio-<uuid>` form or Path A).
|
||||
**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:3900` fails, use the `garage-<uuid>` form or Path A).
|
||||
|
||||
6. Match credentials and bucket:
|
||||
|
||||
```env
|
||||
S3_BUCKET=beenvoice-receipts
|
||||
S3_ACCESS_KEY=<MINIO_ROOT_USER>
|
||||
S3_SECRET_KEY=<MINIO_ROOT_PASSWORD>
|
||||
S3_REGION=us-east-1
|
||||
S3_ACCESS_KEY=<S3_ACCESS_KEY on Garage stack>
|
||||
S3_SECRET_KEY=<S3_SECRET_KEY on Garage stack>
|
||||
S3_REGION=garage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended long-term — one Compose stack
|
||||
|
||||
Deploy **[`docker-compose.coolify.yml`](../docker-compose.coolify.yml)** as **one** Coolify **Docker Compose** resource (app + Postgres + MinIO + minio-init). This is the lowest-friction production layout on Coolify.
|
||||
Deploy **[`docker-compose.coolify.yml`](../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 Resource** → **Docker Compose**
|
||||
2. Point at this repo; compose file: **`docker-compose.coolify.yml`**
|
||||
3. Set env vars from [`.env.example`](../.env.example): `AUTH_SECRET`, `POSTGRES_PASSWORD`, `MINIO_ROOT_*`, etc.
|
||||
3. Set env vars from [`.env.example`](../.env.example): `AUTH_SECRET`, `POSTGRES_PASSWORD`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, etc.
|
||||
4. Assign a domain to the **`app`** service (Coolify fills `SERVICE_URL_APP` / `BETTER_AUTH_URL` automatically).
|
||||
5. **Do not** override `S3_ENDPOINT` — the compose file sets `S3_ENDPOINT=http://minio:9000` on the shared network.
|
||||
5. **Do not** override `S3_ENDPOINT` — the compose file sets `S3_ENDPOINT=http://garage:3900` on the shared network.
|
||||
6. Redeploy.
|
||||
|
||||
Alternative: [`docker-compose.yml`](../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/MinIO.
|
||||
Alternative: [`docker-compose.yml`](../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 + MinIO
|
||||
### Migrating from Application + external Postgres + Garage (or legacy MinIO)
|
||||
|
||||
| Current | Action |
|
||||
|---------|--------|
|
||||
| Beevoice Application | Remove after Compose stack is live |
|
||||
| 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 |
|
||||
| MinIO compose | Remove after data migrated or re-point receipts (new bucket) |
|
||||
| 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 uses port **3900** (not 9000) and 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
|
||||
@@ -103,33 +105,33 @@ Alternative: [`docker-compose.yml`](../docker-compose.yml) works the same way; `
|
||||
|------|---------|
|
||||
| [`docker-compose.coolify.yml`](../docker-compose.coolify.yml) | **Recommended** — full stack for one Coolify Compose resource |
|
||||
| [`docker-compose.yml`](../docker-compose.yml) | Full stack (local/VPS); also valid on Coolify |
|
||||
| [`docker-compose.coolify-minio.yml`](../docker-compose.coolify-minio.yml) | MinIO + bucket init only; pair with Beevoice Application (Path A or B above) |
|
||||
| [`docker-compose.coolify-garage.yml`](../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 MinIO)
|
||||
## Checklist (Application + separate Garage)
|
||||
|
||||
- [ ] MinIO stack redeployed with current `docker-compose.coolify-minio.yml`
|
||||
- [ ] **Path A:** domain on port 9000 + `S3_ENDPOINT` = `SERVICE_URL_MINIO_9000`
|
||||
**or Path B:** Connect to Predefined Network on **both** resources + `S3_ENDPOINT=http://minio-<uuid>:9000`
|
||||
- [ ] `S3_ENDPOINT` is **not** `http://minio:9000`, **not** `localhost`
|
||||
- [ ] `S3_ACCESS_KEY` / `S3_SECRET_KEY` match `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD`
|
||||
- [ ] `S3_BUCKET` exists (`minio-init` creates `beenvoice-receipts` by default)
|
||||
- [ ] Redeployed Beevoice after env or network changes
|
||||
- [ ] Garage stack redeployed with current `docker-compose.coolify-garage.yml`
|
||||
- [ ] **Path A:** domain on port 3900 + `S3_ENDPOINT` = `SERVICE_URL_GARAGE_3900`
|
||||
**or Path B:** Connect to Predefined Network on **both** resources + `S3_ENDPOINT=http://garage-<uuid>:3900`
|
||||
- [ ] `S3_ENDPOINT` is **not** `http://garage:3900`, **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 Beevoice container
|
||||
## Verify from the beenvoice container
|
||||
|
||||
```bash
|
||||
# Shell into Beevoice app container on the Coolify server
|
||||
# Shell into beenvoice app container on the Coolify server
|
||||
docker exec -it <beenvoice-container> sh
|
||||
|
||||
# Path A — public URL (include scheme; path is /minio/health/live on API port)
|
||||
wget -qO- "https://s3.yourdomain.com/minio/health/live" || curl -sf "https://s3.yourdomain.com/minio/health/live"
|
||||
# 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 (no scheme/port in HOST)
|
||||
wget -qO- "http://minio-<uuid>:9000/minio/health/live" || curl -sf "http://minio-<uuid>:9000/minio/health/live"
|
||||
# Path B — internal host from S3_ENDPOINT
|
||||
wget -qO- "http://garage-<uuid>:3900" || curl -sf "http://garage-<uuid>:3900"
|
||||
```
|
||||
|
||||
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 `minio` in production.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user