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:
+20
-23
@@ -4,10 +4,10 @@
|
||||
#
|
||||
# Quick start (local dev):
|
||||
# cp .env.example .env.local
|
||||
# docker compose -f docker-compose.dev.yml up -d # Postgres + MinIO
|
||||
# docker compose -f docker-compose.dev.yml up -d # Postgres + Garage
|
||||
# bun run db:push # or: bun run db:migrate
|
||||
# bun run dev
|
||||
# MinIO console: http://localhost:9001 (minioadmin / minioadmin)
|
||||
# Garage S3 API: http://localhost:3900
|
||||
#
|
||||
# Quick start (Docker app + Postgres):
|
||||
# cp .env.example .env
|
||||
@@ -66,8 +66,7 @@ DB_DISABLE_SSL=true
|
||||
|
||||
# Dev-only: host ports for `docker compose -f docker-compose.dev.yml`.
|
||||
POSTGRES_PORT=5432
|
||||
MINIO_API_PORT=9000
|
||||
MINIO_CONSOLE_PORT=9001
|
||||
GARAGE_API_PORT=3900
|
||||
|
||||
# Optional: if Next dev picks another port, you do not need to change URLs for
|
||||
# sign-in — the auth client uses window.location.origin in the browser.
|
||||
@@ -131,30 +130,28 @@ NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.umami.is/script.js
|
||||
# Receipt storage — S3-compatible (optional)
|
||||
# =============================================================================
|
||||
# When S3_BUCKET + S3_ACCESS_KEY + S3_SECRET_KEY are unset, receipts land in
|
||||
# .data/receipts/ (dev-friendly). Works with AWS S3, MinIO, Cloudflare R2, etc.
|
||||
# .data/receipts/ (dev-friendly). Works with AWS S3, Garage, Cloudflare R2, etc.
|
||||
#
|
||||
# S3_ENDPOINT — who can reach MinIO?
|
||||
# • Host dev (bun dev + docker-compose.dev.yml MinIO on the host): localhost:9000
|
||||
# • App in Docker (docker-compose.yml): http://minio:9000 (Compose service name)
|
||||
# S3_ENDPOINT — who can reach Garage?
|
||||
# • Host dev (bun dev + docker-compose.dev.yml Garage on the host): localhost:3900
|
||||
# • App in Docker (docker-compose.yml): http://garage:3900 (Compose service name)
|
||||
# • Coolify — see docs/COOLIFY.md. Summary:
|
||||
# - Best: one Compose resource with docker-compose.coolify.yml (app+db+minio).
|
||||
# - Application + separate MinIO: ENOTFOUND minio → set S3_ENDPOINT to
|
||||
# SERVICE_URL_MINIO_9000 (public domain) OR http://minio-<resource-uuid>:9000
|
||||
# with Connect to Predefined Network on both resources. Never bare "minio".
|
||||
# - NEVER use localhost in production — inside the app container that is the app, not MinIO.
|
||||
# - Best: one Compose resource with docker-compose.coolify.yml (app+db+garage).
|
||||
# - Application + separate Garage: ENOTFOUND garage → set S3_ENDPOINT to
|
||||
# SERVICE_URL_GARAGE_3900 (public domain) OR http://garage-<resource-uuid>:3900
|
||||
# with Connect to Predefined Network on both resources. Never bare "garage".
|
||||
# - NEVER use localhost in production — inside the app container that is the app, not Garage.
|
||||
#
|
||||
# Local dev with docker-compose.dev.yml MinIO (host `bun dev`):
|
||||
S3_ENDPOINT=http://localhost:9000
|
||||
# Local dev with docker-compose.dev.yml Garage (host `bun dev`):
|
||||
S3_ENDPOINT=http://localhost:3900
|
||||
S3_BUCKET=beenvoice-receipts
|
||||
S3_ACCESS_KEY=minioadmin
|
||||
S3_SECRET_KEY=minioadmin
|
||||
S3_REGION=us-east-1
|
||||
# S3_FORCE_PATH_STYLE=true # default on when S3_ENDPOINT is set; required for MinIO/HTTPS proxy
|
||||
S3_ACCESS_KEY=GK3515373e4c851ebaad366558
|
||||
S3_SECRET_KEY=7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34
|
||||
S3_REGION=garage
|
||||
# S3_FORCE_PATH_STYLE=true # default on when S3_ENDPOINT is set; required for Garage/HTTPS proxy
|
||||
#
|
||||
# docker-compose.yml sets S3_ENDPOINT=http://minio:9000 inside the app container
|
||||
# automatically. MINIO_ROOT_* below must match S3_ACCESS_KEY / S3_SECRET_KEY.
|
||||
MINIO_ROOT_USER=minioadmin
|
||||
MINIO_ROOT_PASSWORD=minioadmin
|
||||
# docker-compose.yml sets S3_ENDPOINT=http://garage:3900 inside the app container
|
||||
# automatically. S3_ACCESS_KEY / S3_SECRET_KEY must match the garage service env.
|
||||
|
||||
# =============================================================================
|
||||
# SSO — Authentik OIDC (optional)
|
||||
|
||||
@@ -162,7 +162,7 @@ To verify migration files match the journal before deploy: `bun run db:verify-jo
|
||||
|
||||
### Coolify
|
||||
|
||||
For self-hosted [Coolify](https://coolify.io) deploys (especially `ENOTFOUND minio` with Application + separate MinIO compose), see **[docs/COOLIFY.md](./docs/COOLIFY.md)**. Recommended: deploy [`docker-compose.coolify.yml`](./docker-compose.coolify.yml) as a single Compose resource.
|
||||
For self-hosted [Coolify](https://coolify.io) deploys (especially `ENOTFOUND garage` with Application + separate Garage compose), see **[docs/COOLIFY.md](./docs/COOLIFY.md)**. Recommended: deploy [`docker-compose.coolify.yml`](./docker-compose.coolify.yml) as a single Compose resource.
|
||||
|
||||
### 4. Sign-ups
|
||||
|
||||
@@ -194,9 +194,9 @@ beenvoice-web/
|
||||
├── src/lib/ # auth, PDF, email, branding helpers
|
||||
├── drizzle/ # SQL migrations
|
||||
├── Dockerfile # Production image (migrate + next start)
|
||||
├── docker-compose.yml # App + Postgres + MinIO (deploy)
|
||||
├── docker-compose.coolify.yml # Coolify Compose (app + db + minio)
|
||||
├── docker-compose.coolify-minio.yml # MinIO-only for Coolify Application pairing
|
||||
├── docker-compose.yml # App + Postgres + Garage (deploy)
|
||||
├── docker-compose.coolify.yml # Coolify Compose (app + db + garage)
|
||||
├── docker-compose.coolify-garage.yml # Garage-only for Coolify Application pairing
|
||||
├── docker-compose.dev.yml # Postgres only (local dev)
|
||||
└── docs/ # Architecture and UI guides
|
||||
```
|
||||
@@ -256,7 +256,7 @@ Business logic lives in `src/server/api/routers/` with Zod validation.
|
||||
| Doc | Contents |
|
||||
|-----|----------|
|
||||
| [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) | Stack, routers, schema, auth, Docker, MCP |
|
||||
| [docs/COOLIFY.md](./docs/COOLIFY.md) | Coolify deploy paths and MinIO networking |
|
||||
| [docs/COOLIFY.md](./docs/COOLIFY.md) | Coolify deploy paths and Garage networking |
|
||||
| [docs/README.md](./docs/README.md) | Index of UI and product guides |
|
||||
| [AGENTS.md](./AGENTS.md) | Conventions for AI-assisted development |
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Garage-only stack for Coolify when beenvoice runs as a separate Application resource.
|
||||
#
|
||||
# Deploy: Coolify → Docker Compose → compose file: docker-compose.coolify-garage.yml
|
||||
#
|
||||
# ── Pair with a beenvoice Application (pick ONE) ───────────────────────────────
|
||||
#
|
||||
# A) Public Garage URL (most reliable — no shared Docker network required)
|
||||
# 1. Redeploy this stack (includes SERVICE_FQDN_GARAGE_3900 below).
|
||||
# 2. Garage resource → assign a domain for port 3900 (e.g. s3.example.com).
|
||||
# 3. Copy SERVICE_URL_GARAGE_3900 from this resource's Environment tab.
|
||||
# 4. beenvoice Application → S3_ENDPOINT=<that URL> → redeploy beenvoice.
|
||||
#
|
||||
# B) Internal Docker DNS (same Coolify destination network)
|
||||
# 1. Garage resource → Advanced → enable "Connect to Predefined Network" → redeploy.
|
||||
# 2. beenvoice Application → same destination → enable "Connect to Predefined Network".
|
||||
# 3. beenvoice → S3_ENDPOINT=http://garage-<GARAGE_RESOURCE_UUID>:3900
|
||||
#
|
||||
# Recommended long-term: deploy docker-compose.coolify.yml as one stack (app+db+garage).
|
||||
# See docs/COOLIFY.md.
|
||||
services:
|
||||
garage:
|
||||
image: dxflrs/garage:v2.3.0
|
||||
environment:
|
||||
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
|
||||
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
|
||||
GARAGE_DEFAULT_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
SERVICE_FQDN_GARAGE_3900:
|
||||
configs:
|
||||
- source: garage_config
|
||||
target: /etc/garage.toml
|
||||
volumes:
|
||||
- beenvoice_garage_meta:/var/lib/garage/meta
|
||||
- beenvoice_garage_data:/var/lib/garage/data
|
||||
command: ["/garage", "server", "--single-node", "--default-bucket"]
|
||||
expose:
|
||||
- "3900"
|
||||
healthcheck:
|
||||
test: ["CMD", "/garage", "status"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 15
|
||||
start_period: 20s
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
beenvoice_garage_meta:
|
||||
beenvoice_garage_data:
|
||||
|
||||
configs:
|
||||
garage_config:
|
||||
content: |
|
||||
metadata_dir = "/var/lib/garage/meta"
|
||||
data_dir = "/var/lib/garage/data"
|
||||
db_engine = "sqlite"
|
||||
replication_factor = 1
|
||||
|
||||
rpc_bind_addr = "[::]:3901"
|
||||
rpc_public_addr = "garage:3901"
|
||||
rpc_secret = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
|
||||
|
||||
[s3_api]
|
||||
s3_region = "garage"
|
||||
api_bind_addr = "[::]:3900"
|
||||
root_domain = ".s3.garage"
|
||||
|
||||
[s3_web]
|
||||
bind_addr = "[::]:3902"
|
||||
root_domain = ".web.garage"
|
||||
index = "index.html"
|
||||
|
||||
[admin]
|
||||
api_bind_addr = "[::]:3903"
|
||||
admin_token = "beenvoice_garage_admin_token_change_me_in_production"
|
||||
metrics_token = "beenvoice_garage_metrics_token_change_me_in_production"
|
||||
@@ -1,63 +0,0 @@
|
||||
# MinIO-only stack for Coolify when Beevoice runs as a separate Application resource.
|
||||
#
|
||||
# Deploy: Coolify → Docker Compose → compose file: docker-compose.coolify-minio.yml
|
||||
#
|
||||
# ── Fix ENOTFOUND minio on a Beevoice Application (pick ONE) ─────────────────
|
||||
#
|
||||
# A) Public MinIO URL (most reliable — no shared Docker network required)
|
||||
# 1. Redeploy this stack (includes SERVICE_FQDN_MINIO_9000 below).
|
||||
# 2. MinIO resource → assign a domain for port 9000 (e.g. s3.example.com).
|
||||
# 3. Open this resource's Environment tab → copy SERVICE_URL_MINIO_9000
|
||||
# (e.g. https://s3.example.com).
|
||||
# 4. Beevoice Application → S3_ENDPOINT=<that URL> → redeploy Beevoice.
|
||||
#
|
||||
# B) Internal Docker DNS (same Coolify destination network)
|
||||
# 1. MinIO resource → Advanced → enable "Connect to Predefined Network" → redeploy.
|
||||
# 2. Beevoice Application → same destination → enable "Connect to Predefined Network"
|
||||
# → redeploy.
|
||||
# 3. Beevoice → S3_ENDPOINT=http://minio-<MINIO_RESOURCE_UUID>:9000
|
||||
# (UUID is in the MinIO resource URL / COOLIFY_RESOURCE_UUID — NOT bare "minio").
|
||||
#
|
||||
# Recommended long-term: deploy docker-compose.coolify.yml as one stack (app+db+minio).
|
||||
# See docs/COOLIFY.md.
|
||||
services:
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
SERVICE_FQDN_MINIO_9000:
|
||||
SERVICE_FQDN_MINIO_9001:
|
||||
volumes:
|
||||
- beenvoice_minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
expose:
|
||||
- "9000"
|
||||
- "9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- >-
|
||||
mc alias set local http://minio:9000
|
||||
"$${MINIO_ROOT_USER:-minioadmin}"
|
||||
"$${MINIO_ROOT_PASSWORD:-minioadmin}" &&
|
||||
mc mb "local/$${S3_BUCKET:-beenvoice-receipts}" --ignore-existing
|
||||
restart: "no"
|
||||
|
||||
volumes:
|
||||
beenvoice_minio_data:
|
||||
+54
-42
@@ -1,13 +1,13 @@
|
||||
# Beevoice on Coolify — single Docker Compose resource (recommended).
|
||||
# beenvoice on Coolify — single Docker Compose resource (recommended).
|
||||
#
|
||||
# Deploy: Coolify → New Resource → Docker Compose → compose file: docker-compose.coolify.yml
|
||||
#
|
||||
# 1. Assign a domain to the `app` service in Coolify (SERVICE_FQDN_APP wires Traefik).
|
||||
# 2. Set AUTH_SECRET, POSTGRES_PASSWORD, MINIO_ROOT_* in the resource env (see .env.example).
|
||||
# 3. Do NOT override S3_ENDPOINT — this stack sets http://minio:9000 on the shared network.
|
||||
# 2. Set AUTH_SECRET, POSTGRES_PASSWORD, S3_ACCESS_KEY, S3_SECRET_KEY in the resource env (see .env.example).
|
||||
# 3. Do NOT override S3_ENDPOINT — this stack sets http://garage:3900 on the shared network.
|
||||
# 4. Rebuild after changing NEXT_PUBLIC_* (image build args use SERVICE_URL_APP).
|
||||
#
|
||||
# Migrating from Application + separate Postgres + MinIO compose:
|
||||
# Migrating from Application + separate Postgres + Garage compose:
|
||||
# - Export Postgres data, point DATABASE_URL at this stack's `db` service, redeploy once here.
|
||||
# - Or keep external Postgres and remove the `db` service + volume from this file.
|
||||
services:
|
||||
@@ -36,20 +36,18 @@ services:
|
||||
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID:-}
|
||||
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET:-}
|
||||
AUTHENTIK_ORIGIN: ${AUTHENTIK_ORIGIN:-}
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_ENDPOINT: http://garage:3900
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
S3_ACCESS_KEY: ${MINIO_ROOT_USER:-minioadmin}
|
||||
S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_REGION: ${S3_REGION:-us-east-1}
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
|
||||
S3_REGION: ${S3_REGION:-garage}
|
||||
expose:
|
||||
- "3000"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
garage:
|
||||
condition: service_healthy
|
||||
minio-init:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
@@ -68,44 +66,58 @@ services:
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
# Optional: assign domains in Coolify for console / external S3 API access.
|
||||
garage:
|
||||
image: dxflrs/garage:v2.3.0
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
SERVICE_FQDN_MINIO_9001:
|
||||
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
|
||||
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
|
||||
GARAGE_DEFAULT_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
SERVICE_FQDN_GARAGE_3900:
|
||||
configs:
|
||||
- source: garage_config
|
||||
target: /etc/garage.toml
|
||||
volumes:
|
||||
- beenvoice_minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
- beenvoice_garage_meta:/var/lib/garage/meta
|
||||
- beenvoice_garage_data:/var/lib/garage/data
|
||||
command: ["/garage", "server", "--single-node", "--default-bucket"]
|
||||
expose:
|
||||
- "9000"
|
||||
- "9001"
|
||||
- "3900"
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
test: ["CMD", "/garage", "status"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
retries: 15
|
||||
start_period: 20s
|
||||
restart: unless-stopped
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- >-
|
||||
mc alias set local http://minio:9000
|
||||
"$${MINIO_ROOT_USER:-minioadmin}"
|
||||
"$${MINIO_ROOT_PASSWORD:-minioadmin}" &&
|
||||
mc mb "local/$${S3_BUCKET:-beenvoice-receipts}" --ignore-existing
|
||||
restart: "no"
|
||||
|
||||
volumes:
|
||||
beenvoice_pg_data:
|
||||
beenvoice_minio_data:
|
||||
beenvoice_garage_meta:
|
||||
beenvoice_garage_data:
|
||||
|
||||
configs:
|
||||
garage_config:
|
||||
content: |
|
||||
metadata_dir = "/var/lib/garage/meta"
|
||||
data_dir = "/var/lib/garage/data"
|
||||
db_engine = "sqlite"
|
||||
replication_factor = 1
|
||||
|
||||
rpc_bind_addr = "[::]:3901"
|
||||
rpc_public_addr = "garage:3901"
|
||||
rpc_secret = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
|
||||
|
||||
[s3_api]
|
||||
s3_region = "garage"
|
||||
api_bind_addr = "[::]:3900"
|
||||
root_domain = ".s3.garage"
|
||||
|
||||
[s3_web]
|
||||
bind_addr = "[::]:3902"
|
||||
root_domain = ".web.garage"
|
||||
index = "index.html"
|
||||
|
||||
[admin]
|
||||
api_bind_addr = "[::]:3903"
|
||||
admin_token = "beenvoice_garage_admin_token_change_me_in_production"
|
||||
metrics_token = "beenvoice_garage_metrics_token_change_me_in_production"
|
||||
|
||||
+45
-30
@@ -17,43 +17,58 @@ services:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
restart: unless-stopped
|
||||
|
||||
# S3-compatible receipt storage for host dev (`bun dev`). API :9000, console :9001.
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
# S3-compatible receipt storage for host dev (`bun dev`). API :3900.
|
||||
garage:
|
||||
image: dxflrs/garage:v2.3.0
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
|
||||
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
|
||||
GARAGE_DEFAULT_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
configs:
|
||||
- source: garage_config
|
||||
target: /etc/garage.toml
|
||||
volumes:
|
||||
- beenvoice_dev_minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
- beenvoice_dev_garage_meta:/var/lib/garage/meta
|
||||
- beenvoice_dev_garage_data:/var/lib/garage/data
|
||||
command: ["/garage", "server", "--single-node", "--default-bucket"]
|
||||
ports:
|
||||
- "${MINIO_API_PORT:-9000}:9000"
|
||||
- "${MINIO_CONSOLE_PORT:-9001}:9001"
|
||||
- "${GARAGE_API_PORT:-3900}:3900"
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
test: ["CMD", "/garage", "status"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
retries: 15
|
||||
start_period: 20s
|
||||
restart: unless-stopped
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- >-
|
||||
mc alias set local http://minio:9000
|
||||
"$${MINIO_ROOT_USER:-minioadmin}"
|
||||
"$${MINIO_ROOT_PASSWORD:-minioadmin}" &&
|
||||
mc mb "local/$${S3_BUCKET:-beenvoice-receipts}" --ignore-existing
|
||||
restart: "no"
|
||||
|
||||
volumes:
|
||||
beenvoice_dev_pg_data:
|
||||
beenvoice_dev_minio_data:
|
||||
beenvoice_dev_garage_meta:
|
||||
beenvoice_dev_garage_data:
|
||||
|
||||
configs:
|
||||
garage_config:
|
||||
content: |
|
||||
metadata_dir = "/var/lib/garage/meta"
|
||||
data_dir = "/var/lib/garage/data"
|
||||
db_engine = "sqlite"
|
||||
replication_factor = 1
|
||||
|
||||
rpc_bind_addr = "[::]:3901"
|
||||
rpc_public_addr = "garage:3901"
|
||||
rpc_secret = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
|
||||
|
||||
[s3_api]
|
||||
s3_region = "garage"
|
||||
api_bind_addr = "[::]:3900"
|
||||
root_domain = ".s3.garage"
|
||||
|
||||
[s3_web]
|
||||
bind_addr = "[::]:3902"
|
||||
root_domain = ".web.garage"
|
||||
index = "index.html"
|
||||
|
||||
[admin]
|
||||
api_bind_addr = "[::]:3903"
|
||||
admin_token = "beenvoice_garage_admin_token_change_me_in_production"
|
||||
metrics_token = "beenvoice_garage_metrics_token_change_me_in_production"
|
||||
|
||||
+53
-40
@@ -1,8 +1,8 @@
|
||||
# Production stack (app + Postgres + MinIO). Local dev Postgres/MinIO: docker-compose.dev.yml
|
||||
# Production stack (app + Postgres + Garage). Local dev Postgres/Garage: docker-compose.dev.yml
|
||||
#
|
||||
# Coolify: deploy docker-compose.coolify.yml as ONE Docker Compose resource (preferred),
|
||||
# or this file. S3_ENDPOINT=http://minio:9000 works only inside a single stack.
|
||||
# Application + separate MinIO → docs/COOLIFY.md.
|
||||
# or this file. S3_ENDPOINT=http://garage:3900 works only inside a single stack.
|
||||
# Application + separate Garage → docs/COOLIFY.md.
|
||||
#
|
||||
# After git pull, rebuild before starting — a plain `docker compose up -d` reuses
|
||||
# the existing local image and will NOT include new code. Use:
|
||||
@@ -35,20 +35,18 @@ services:
|
||||
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID:-}
|
||||
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET:-}
|
||||
AUTHENTIK_ORIGIN: ${AUTHENTIK_ORIGIN:-}
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_ENDPOINT: http://garage:3900
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
S3_ACCESS_KEY: ${MINIO_ROOT_USER:-minioadmin}
|
||||
S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_REGION: ${S3_REGION:-us-east-1}
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
|
||||
S3_REGION: ${S3_REGION:-garage}
|
||||
ports:
|
||||
- "${WEB_PORT:-${PORT:-3000}}:3000"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
garage:
|
||||
condition: service_healthy
|
||||
minio-init:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
@@ -67,43 +65,58 @@ services:
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
# S3-compatible receipt storage. API :9000, web console :9001 (host-mapped in dev compose).
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
# S3-compatible receipt storage (~50–100 MB RAM vs MinIO). API :3900.
|
||||
garage:
|
||||
image: dxflrs/garage:v2.3.0
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
|
||||
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
|
||||
GARAGE_DEFAULT_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
configs:
|
||||
- source: garage_config
|
||||
target: /etc/garage.toml
|
||||
volumes:
|
||||
- beenvoice_minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
- beenvoice_garage_meta:/var/lib/garage/meta
|
||||
- beenvoice_garage_data:/var/lib/garage/data
|
||||
command: ["/garage", "server", "--single-node", "--default-bucket"]
|
||||
ports:
|
||||
- "${MINIO_API_PORT:-9000}:9000"
|
||||
- "${MINIO_CONSOLE_PORT:-9001}:9001"
|
||||
- "${GARAGE_API_PORT:-3900}:3900"
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
test: ["CMD", "/garage", "status"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
retries: 15
|
||||
start_period: 20s
|
||||
restart: unless-stopped
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- >-
|
||||
mc alias set local http://minio:9000
|
||||
"$${MINIO_ROOT_USER:-minioadmin}"
|
||||
"$${MINIO_ROOT_PASSWORD:-minioadmin}" &&
|
||||
mc mb "local/$${S3_BUCKET:-beenvoice-receipts}" --ignore-existing
|
||||
restart: "no"
|
||||
|
||||
volumes:
|
||||
beenvoice_pg_data:
|
||||
beenvoice_minio_data:
|
||||
beenvoice_garage_meta:
|
||||
beenvoice_garage_data:
|
||||
|
||||
configs:
|
||||
garage_config:
|
||||
content: |
|
||||
metadata_dir = "/var/lib/garage/meta"
|
||||
data_dir = "/var/lib/garage/data"
|
||||
db_engine = "sqlite"
|
||||
replication_factor = 1
|
||||
|
||||
rpc_bind_addr = "[::]:3901"
|
||||
rpc_public_addr = "garage:3901"
|
||||
rpc_secret = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
|
||||
|
||||
[s3_api]
|
||||
s3_region = "garage"
|
||||
api_bind_addr = "[::]:3900"
|
||||
root_domain = ".s3.garage"
|
||||
|
||||
[s3_web]
|
||||
bind_addr = "[::]:3902"
|
||||
root_domain = ".web.garage"
|
||||
index = "index.html"
|
||||
|
||||
[admin]
|
||||
api_bind_addr = "[::]:3903"
|
||||
admin_token = "beenvoice_garage_admin_token_change_me_in_production"
|
||||
metrics_token = "beenvoice_garage_metrics_token_change_me_in_production"
|
||||
|
||||
+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.
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|----------|-------------|
|
||||
| [ARCHITECTURE.md](./ARCHITECTURE.md) | Server stack, tRPC routers, schema, auth, MCP, Docker, mobile API contract |
|
||||
| [../README.md](../README.md) | Install, scripts, deployment |
|
||||
| [COOLIFY.md](./COOLIFY.md) | Coolify + MinIO networking (`ENOTFOUND minio`) |
|
||||
| [COOLIFY.md](./COOLIFY.md) | Coolify + Garage networking (`ENOTFOUND garage`) |
|
||||
|
||||
## UI & product guides
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ set -euo pipefail
|
||||
|
||||
# Production deploy helper for docker-compose.yml (not docker-compose.dev.yml).
|
||||
# Rebuilds the app image from the current working tree, then starts/restarts services
|
||||
# (app, db, minio, minio-init). Receipt storage uses in-stack MinIO unless S3_* are
|
||||
# overridden in .env. MinIO API/console: localhost:${MINIO_API_PORT:-9000} / :9001.
|
||||
# (app, db, garage). Receipt storage uses in-stack Garage unless S3_* are
|
||||
# overridden in .env. Garage S3 API: localhost:${GARAGE_API_PORT:-3900}.
|
||||
#
|
||||
# Plain `docker compose up -d` reuses the local image tag and does NOT pick up
|
||||
# changes from `git pull`. Always pass --build or use this script after pulling.
|
||||
|
||||
+11
-11
@@ -22,7 +22,7 @@ type S3Module = typeof import("@aws-sdk/client-s3");
|
||||
let s3ModulePromise: Promise<S3Module> | null = null;
|
||||
let s3Client: InstanceType<S3Module["S3Client"]> | null = null;
|
||||
let s3DnsHintLogged = false;
|
||||
let s3BareMinioHintLogged = false;
|
||||
let s3BareGarageHintLogged = false;
|
||||
|
||||
function shouldForcePathStyle(): boolean {
|
||||
const override = process.env.S3_FORCE_PATH_STYLE?.trim().toLowerCase();
|
||||
@@ -31,19 +31,19 @@ function shouldForcePathStyle(): boolean {
|
||||
return Boolean(process.env.S3_ENDPOINT);
|
||||
}
|
||||
|
||||
function logBareMinioEndpointHint(): void {
|
||||
if (s3BareMinioHintLogged || process.env.NODE_ENV !== "production") return;
|
||||
function logBareGarageEndpointHint(): void {
|
||||
if (s3BareGarageHintLogged || process.env.NODE_ENV !== "production") return;
|
||||
const endpoint = process.env.S3_ENDPOINT;
|
||||
if (!endpoint) return;
|
||||
try {
|
||||
const { hostname } = new URL(endpoint);
|
||||
if (hostname !== "minio") return;
|
||||
s3BareMinioHintLogged = true;
|
||||
if (hostname !== "garage") return;
|
||||
s3BareGarageHintLogged = true;
|
||||
console.warn(
|
||||
"[object-storage] S3_ENDPOINT hostname is bare 'minio'. " +
|
||||
"[object-storage] S3_ENDPOINT hostname is bare 'garage'. " +
|
||||
"That only resolves inside a single Docker Compose stack. " +
|
||||
"Coolify Application + separate MinIO compose: set S3_ENDPOINT to " +
|
||||
"SERVICE_URL_MINIO_9000 (public domain) or http://minio-<resource-uuid>:9000. " +
|
||||
"Coolify Application + separate Garage compose: set S3_ENDPOINT to " +
|
||||
"SERVICE_URL_GARAGE_3900 (public domain) or http://garage-<resource-uuid>:3900. " +
|
||||
"See docs/COOLIFY.md.",
|
||||
);
|
||||
} catch {
|
||||
@@ -59,7 +59,7 @@ function logS3DnsHint(error: unknown): void {
|
||||
const endpoint = process.env.S3_ENDPOINT ?? "(AWS default)";
|
||||
console.error(
|
||||
`[object-storage] S3 DNS failed (${code}) for endpoint ${endpoint}. ` +
|
||||
"Separate Coolify stacks cannot resolve bare 'minio' — use the internal hostname from the MinIO resource UI and enable Connect to Predefined Network on the app. See docs/COOLIFY.md.",
|
||||
"Separate Coolify stacks cannot resolve bare 'garage' — use the internal hostname from the Garage resource UI and enable Connect to Predefined Network on the app. See docs/COOLIFY.md.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ async function getS3() {
|
||||
s3ModulePromise ??= import("@aws-sdk/client-s3");
|
||||
const mod = await s3ModulePromise;
|
||||
if (!s3Client) {
|
||||
logBareMinioEndpointHint();
|
||||
logBareGarageEndpointHint();
|
||||
s3Client = new mod.S3Client({
|
||||
region: process.env.S3_REGION ?? "us-east-1",
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
@@ -84,7 +84,7 @@ async function getS3() {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY!,
|
||||
secretAccessKey: process.env.S3_SECRET_KEY!,
|
||||
},
|
||||
// Required for MinIO and most S3-compatible endpoints (including HTTPS proxies).
|
||||
// Required for Garage and most S3-compatible endpoints (including HTTPS proxies).
|
||||
forcePathStyle: shouldForcePathStyle(),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user