enable react compiler
This commit is contained in:
+17
-2
@@ -130,8 +130,23 @@ NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.umami.is/script.js
|
||||
# =============================================================================
|
||||
# Receipt storage — S3-compatible (optional)
|
||||
# =============================================================================
|
||||
# When unset, receipt files are stored locally in .data/receipts/ (dev-friendly).
|
||||
# Works with AWS S3, MinIO, Cloudflare R2, etc.
|
||||
# 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.
|
||||
#
|
||||
# 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)
|
||||
# • Coolify — see docs/COOLIFY.md for full steps. Summary:
|
||||
# - Best: one Compose resource with docker-compose.yml (app+db+minio); do not override S3_ENDPOINT.
|
||||
# - App + separate MinIO stack: ENOTFOUND minio means the app is not on MinIO's Docker network.
|
||||
# Fix: Beevoice Application → enable "Connect to Predefined Network" (same destination as MinIO),
|
||||
# set S3_ENDPOINT=http://<internal-host-from-minio-ui>:9000 (often NOT bare "minio").
|
||||
# - NEVER use localhost in production — inside the app container that is the app, not MinIO.
|
||||
# Troubleshooting getaddrinfo ENOTFOUND minio:
|
||||
# 1) Confirm Beevoice and MinIO are same Coolify project/destination
|
||||
# 2) Enable Connect to Predefined Network on Beevoice; redeploy
|
||||
# 3) Copy hostname from MinIO resource internal URL → S3_ENDPOINT (http://HOST:9000)
|
||||
# 4) Or deploy docker-compose.yml as a single stack instead
|
||||
#
|
||||
# Local dev with docker-compose.dev.yml MinIO (host `bun dev`):
|
||||
S3_ENDPOINT=http://localhost:9000
|
||||
|
||||
@@ -17,10 +17,8 @@ ARG NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
ARG BETTER_AUTH_URL=http://localhost:3000
|
||||
|
||||
# Low-memory Docker build profile:
|
||||
# - disable React Compiler (saves compile RAM; prod image still runs fine without it)
|
||||
# - skip tsc inside `next build` (run `bun run check` in CI instead)
|
||||
ENV DOCKER_BUILD=1 \
|
||||
DISABLE_REACT_COMPILER=1 \
|
||||
NODE_ENV=production \
|
||||
SKIP_ENV_VALIDATION=1 \
|
||||
NEXT_TELEMETRY_DISABLED=1 \
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# MinIO-only stack for Coolify when Beevoice runs as a separate Application resource.
|
||||
#
|
||||
# Deploy: Coolify → Docker Compose → compose file: docker-compose.coolify-minio.yml
|
||||
#
|
||||
# Beevoice (Application) cannot use S3_ENDPOINT=http://minio:9000 unless it shares
|
||||
# this stack's Docker network AND that hostname resolves (usually it does not across
|
||||
# separate Coolify resources). See docs/COOLIFY.md — copy the internal hostname from
|
||||
# the MinIO resource UI into Beevoice's S3_ENDPOINT and enable "Connect to Predefined
|
||||
# Network" on the Beevoice app.
|
||||
#
|
||||
# Recommended alternative: deploy full docker-compose.yml as one Compose resource (app
|
||||
# + db + minio) so S3_ENDPOINT=http://minio:9000 works without extra networking.
|
||||
services:
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
volumes:
|
||||
- beenvoice_minio_data:/data
|
||||
command: server /data --console-address ":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:
|
||||
@@ -19,7 +19,7 @@ services:
|
||||
|
||||
# S3-compatible receipt storage for host dev (`bun dev`). API :9000, console :9001.
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
image: minio/minio:latest
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:RELEASE.2025-04-22T16-22-07Z
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
@@ -45,11 +45,13 @@ services:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
mc alias set local http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD &&
|
||||
mc mb local/$$S3_BUCKET --ignore-existing
|
||||
"
|
||||
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:
|
||||
|
||||
+12
-7
@@ -1,5 +1,8 @@
|
||||
# Production stack (app + Postgres + MinIO). Local dev Postgres/MinIO: docker-compose.dev.yml
|
||||
#
|
||||
# Coolify: deploy this file as ONE Docker Compose resource so S3_ENDPOINT=http://minio:9000 works.
|
||||
# Separate Application + MinIO stacks need shared networking — see 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:
|
||||
# ./scripts/docker-deploy.sh
|
||||
@@ -65,7 +68,7 @@ services:
|
||||
|
||||
# S3-compatible receipt storage. API :9000, web console :9001 (host-mapped in dev compose).
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
image: minio/minio:latest
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
@@ -83,7 +86,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:RELEASE.2025-04-22T16-22-07Z
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
@@ -91,11 +94,13 @@ services:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
mc alias set local http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD &&
|
||||
mc mb local/$$S3_BUCKET --ignore-existing
|
||||
"
|
||||
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:
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
# Coolify deployment — Beevoice + MinIO
|
||||
|
||||
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.
|
||||
|
||||
## Why `getaddrinfo ENOTFOUND minio` happens
|
||||
|
||||
Docker DNS resolves service names **only inside the same Docker network**.
|
||||
|
||||
| Setup | Does `http://minio:9000` work? |
|
||||
|-------|-------------------------------|
|
||||
| Single `docker-compose.yml` stack (app + minio together) | Yes — Compose service name `minio` |
|
||||
| Beevoice **Application** + MinIO **separate Compose** resource | **No** — each Coolify resource gets its own network by default |
|
||||
| Both resources share a Coolify **destination** network + correct hostname | Yes — but hostname is often **not** bare `minio` |
|
||||
|
||||
Setting `S3_ENDPOINT=http://minio:9000` on a standalone Beevoice Application fails because the app container is not on the MinIO stack's internal network. Node's DNS lookup returns `ENOTFOUND minio`.
|
||||
|
||||
Also avoid `http://localhost:9000` inside the app container — that points at the app itself, not MinIO.
|
||||
|
||||
---
|
||||
|
||||
## Recommended: Option C — one Compose stack (simplest)
|
||||
|
||||
Deploy the repo's full [`docker-compose.yml`](../docker-compose.yml) as **one** Coolify **Docker Compose** resource (app + Postgres + MinIO + minio-init).
|
||||
|
||||
1. Coolify → **New Resource** → **Docker Compose**
|
||||
2. Point at this repo; compose file: `docker-compose.yml`
|
||||
3. Set env vars from [`.env.example`](../.env.example) (`AUTH_SECRET`, `BETTER_AUTH_URL`, `NEXT_PUBLIC_APP_URL`, etc.)
|
||||
4. **Do not** override `S3_ENDPOINT` — the compose file sets `S3_ENDPOINT=http://minio:9000` for the app service automatically
|
||||
5. Redeploy
|
||||
|
||||
All services share one Compose network; `minio` resolves correctly.
|
||||
|
||||
---
|
||||
|
||||
## Option A — separate resources, shared Coolify network
|
||||
|
||||
Use when Beevoice stays a standalone **Application** (Dockerfile) and MinIO is a separate Compose resource.
|
||||
|
||||
### 1. Same Coolify project and destination
|
||||
|
||||
Put both resources in the **same Coolify project** and deploy them to the **same destination** (same Docker network / server).
|
||||
|
||||
### 2. Connect the Beevoice app to that network
|
||||
|
||||
On the **Beevoice Application** resource:
|
||||
|
||||
1. Open **Advanced** (or network settings)
|
||||
2. Enable **Connect to Predefined Network**
|
||||
3. Select the **same destination/network** as the MinIO stack
|
||||
4. **Redeploy** the app (required after toggling network)
|
||||
|
||||
The MinIO stack does **not** need this option — only the service that **initiates** connections (Beevoice) needs it.
|
||||
|
||||
### 3. Set `S3_ENDPOINT` to the real internal hostname
|
||||
|
||||
Bare `minio` usually still fails across separate Coolify resources. Use the hostname Coolify assigns on the shared network:
|
||||
|
||||
1. Open the **MinIO Compose** resource in Coolify
|
||||
2. Find the **internal URL** / connection info (eye icon next to internal connection string)
|
||||
3. Copy the **hostname** from that URL (not `localhost`, not bare `minio` unless you verified it resolves)
|
||||
|
||||
Typical patterns:
|
||||
|
||||
| What you see | Use as `S3_ENDPOINT` |
|
||||
|--------------|----------------------|
|
||||
| Internal URL host `minio-abc123def456` | `http://minio-abc123def456:9000` |
|
||||
| Container name `x8k2j4...` (random id) | `http://x8k2j4...:9000` |
|
||||
| Same compose stack only | `http://minio:9000` |
|
||||
|
||||
On the Coolify server you can confirm:
|
||||
|
||||
```bash
|
||||
# List MinIO containers
|
||||
docker ps --filter name=minio
|
||||
|
||||
# See DNS aliases on the shared network (replace CONTAINER and NETWORK)
|
||||
docker inspect CONTAINER --format '{{json .NetworkSettings.Networks}}' | jq
|
||||
```
|
||||
|
||||
Set on the **Beevoice Application** env:
|
||||
|
||||
```env
|
||||
S3_ENDPOINT=http://<hostname-from-step-3>:9000
|
||||
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
|
||||
```
|
||||
|
||||
Redeploy Beevoice after changing env.
|
||||
|
||||
### 4. Enable on MinIO stack too (only if A still fails)
|
||||
|
||||
If the app still cannot resolve the hostname, enable **Connect to Predefined Network** on the **MinIO Compose** resource as well (same destination), redeploy MinIO, then re-check the internal URL — Coolify may expose a `minio-<resource-id>` alias on the shared network.
|
||||
|
||||
---
|
||||
|
||||
## Option B — internal URL from Coolify UI (quick fix)
|
||||
|
||||
Same as Option A step 3, without re-architecting:
|
||||
|
||||
1. MinIO resource → copy **internal** hostname (from internal URL field)
|
||||
2. Beevoice Application → `S3_ENDPOINT=http://<that-hostname>:9000`
|
||||
3. Enable **Connect to Predefined Network** on Beevoice if not already
|
||||
4. Redeploy Beevoice
|
||||
|
||||
If DNS still fails, the app is not on the network where that hostname is registered — go back to Option A or use Option C.
|
||||
|
||||
---
|
||||
|
||||
## Option D — public / external MinIO URL (fallback)
|
||||
|
||||
If internal Docker DNS cannot be made to work:
|
||||
|
||||
```env
|
||||
S3_ENDPOINT=https://minio.yourdomain.com
|
||||
```
|
||||
|
||||
Expose MinIO API (port 9000) via Coolify proxy or a public domain. Less ideal (traffic leaves the Docker network, TLS/path-style config may need tuning) but avoids internal DNS entirely.
|
||||
|
||||
---
|
||||
|
||||
## Separate MinIO-only Compose file
|
||||
|
||||
[`docker-compose.coolify-minio.yml`](../docker-compose.coolify-minio.yml) deploys only MinIO + bucket init for a dedicated Coolify Compose resource. Pair it with a Beevoice Application using Option A or B.
|
||||
|
||||
Do **not** add `networks: coolify: external: true` unless you know the exact external network name on your Coolify 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
|
||||
|
||||
- [ ] Beevoice and MinIO in the same Coolify **project**
|
||||
- [ ] Same **destination** / server
|
||||
- [ ] Beevoice Application: **Connect to Predefined Network** enabled (when MinIO is a separate resource)
|
||||
- [ ] `S3_ENDPOINT` uses internal hostname from Coolify UI — not `localhost`, not unverified `minio`
|
||||
- [ ] `S3_ACCESS_KEY` / `S3_SECRET_KEY` match MinIO `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD`
|
||||
- [ ] `S3_BUCKET` exists ( `minio-init` in compose creates `beenvoice-receipts` by default)
|
||||
- [ ] Redeployed after env or network changes
|
||||
|
||||
## Verify from the Beevoice container
|
||||
|
||||
```bash
|
||||
# Shell into Beevoice app container on Coolify server
|
||||
docker exec -it <beenvoice-container> sh
|
||||
|
||||
# Replace HOST with your S3_ENDPOINT hostname (no scheme/port)
|
||||
wget -qO- "http://HOST:9000/minio/health/live" || curl -sf "http://HOST:9000/minio/health/live"
|
||||
```
|
||||
|
||||
If this fails with "bad address" or timeout, fix networking before debugging app code.
|
||||
@@ -8,6 +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`) |
|
||||
|
||||
## UI & product guides
|
||||
|
||||
|
||||
@@ -21,6 +21,28 @@ type S3Module = typeof import("@aws-sdk/client-s3");
|
||||
|
||||
let s3ModulePromise: Promise<S3Module> | null = null;
|
||||
let s3Client: InstanceType<S3Module["S3Client"]> | null = null;
|
||||
let s3DnsHintLogged = false;
|
||||
|
||||
function logS3DnsHint(error: unknown): void {
|
||||
if (s3DnsHintLogged) return;
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
if (code !== "ENOTFOUND" && code !== "EAI_AGAIN") return;
|
||||
s3DnsHintLogged = true;
|
||||
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.",
|
||||
);
|
||||
}
|
||||
|
||||
async function withS3Diagnostics<T>(operation: () => Promise<T>): Promise<T> {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
logS3DnsHint(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function getS3() {
|
||||
if (!s3ModulePromise) {
|
||||
@@ -53,13 +75,15 @@ export async function putObject(
|
||||
): Promise<void> {
|
||||
if (isS3Configured()) {
|
||||
const { client, PutObjectCommand } = await getS3();
|
||||
await client.send(
|
||||
await withS3Diagnostics(() =>
|
||||
client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: process.env.S3_BUCKET!,
|
||||
Key: key,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
}),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -72,11 +96,13 @@ export async function putObject(
|
||||
export async function getObject(key: string): Promise<Buffer> {
|
||||
if (isS3Configured()) {
|
||||
const { client, GetObjectCommand } = await getS3();
|
||||
const response = await client.send(
|
||||
const response = await withS3Diagnostics(() =>
|
||||
client.send(
|
||||
new GetObjectCommand({
|
||||
Bucket: process.env.S3_BUCKET!,
|
||||
Key: key,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const bytes = await response.Body?.transformToByteArray();
|
||||
if (!bytes) {
|
||||
@@ -91,11 +117,13 @@ export async function getObject(key: string): Promise<Buffer> {
|
||||
export async function deleteObject(key: string): Promise<void> {
|
||||
if (isS3Configured()) {
|
||||
const { client, DeleteObjectCommand } = await getS3();
|
||||
await client.send(
|
||||
await withS3Diagnostics(() =>
|
||||
client.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket: process.env.S3_BUCKET!,
|
||||
Key: key,
|
||||
}),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user