8df292dfee
getPublicUrl() runs in the browser (called from client components rendering recipe thumbnails), but read the plain STORAGE_PUBLIC_URL env var — never inlined into the client bundle, so every browser fell back to the hardcoded localhost:9000 default regardless of the real deployed storage domain, tripping CSP img-src and mixed-content blocks in production. Added a NEXT_PUBLIC_STORAGE_PUBLIC_URL build arg (Dockerfile, compose.prod.yml) wired from the same STORAGE_PUBLIC_URL value, and getPublicUrl() now reads that. Verified locally: building with a fake public storage domain set shows it correctly inlined into the client JS chunk (previously only the localhost fallback ever appeared there).
160 lines
5.5 KiB
YAML
160 lines
5.5 KiB
YAML
services:
|
|
postgres:
|
|
image: postgres:17-alpine
|
|
restart: always
|
|
environment:
|
|
POSTGRES_DB: ${POSTGRES_DB}
|
|
POSTGRES_USER: ${POSTGRES_USER}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
restart: always
|
|
volumes:
|
|
- redis_data:/data
|
|
command: redis-server --save 60 1 --requirepass ${REDIS_PASSWORD}
|
|
|
|
minio:
|
|
image: minio/minio:latest
|
|
restart: always
|
|
environment:
|
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
|
# The browser PUTs photo uploads directly to MinIO using a presigned URL
|
|
# (STORAGE_PUBLIC_URL) — without CORS allowed for that origin, the browser
|
|
# blocks the request. Restricted to the app's own origin, not "*".
|
|
MINIO_API_CORS_ALLOW_ORIGIN: ${BETTER_AUTH_URL}
|
|
ports:
|
|
# Published so the browser can reach MinIO directly for presigned
|
|
# up/downloads — same pattern as `web`'s ${WEB_PORT}: your Traefik LXC
|
|
# routes a (sub)domain to <this-lxc-ip>:${STORAGE_PORT}, and that public
|
|
# URL is what STORAGE_PUBLIC_URL must be set to.
|
|
- "${STORAGE_PORT:-9000}:9000"
|
|
volumes:
|
|
- minio_data:/data
|
|
command: server /data --console-address ":9001"
|
|
healthcheck:
|
|
test: ["CMD", "mc", "ready", "local"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
minio-init:
|
|
image: minio/mc:latest
|
|
depends_on:
|
|
minio:
|
|
condition: service_healthy
|
|
entrypoint: >
|
|
/bin/sh -c "
|
|
mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD};
|
|
mc mb --ignore-existing local/epicure-uploads;
|
|
mc anonymous set download local/epicure-uploads;
|
|
exit 0;
|
|
"
|
|
|
|
migrate:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
target: migrator
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
restart: "no"
|
|
|
|
web:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
# Explicit target: the Dockerfile's last stage is no longer `runner` now
|
|
# that `cron` was appended after it, and the default build target is
|
|
# whatever stage comes last — pin it so this doesn't silently change.
|
|
target: runner
|
|
args:
|
|
NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${NEXT_PUBLIC_VAPID_PUBLIC_KEY}
|
|
NEXT_PUBLIC_GITHUB_ENABLED: ${NEXT_PUBLIC_GITHUB_ENABLED}
|
|
NEXT_PUBLIC_DISCORD_ENABLED: ${NEXT_PUBLIC_DISCORD_ENABLED}
|
|
NEXT_PUBLIC_AUTHENTIK_ENABLED: ${NEXT_PUBLIC_AUTHENTIK_ENABLED}
|
|
STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
|
|
NEXT_PUBLIC_STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
|
|
restart: always
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
|
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
|
STORAGE_ENDPOINT: http://minio:9000
|
|
STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
|
|
STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER}
|
|
STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
|
|
STORAGE_BUCKET: epicure-uploads
|
|
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
|
BETTER_AUTH_URL: ${BETTER_AUTH_URL}
|
|
ENCRYPTION_SECRET: ${ENCRYPTION_SECRET}
|
|
NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${NEXT_PUBLIC_VAPID_PUBLIC_KEY}
|
|
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
|
|
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
|
|
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
|
|
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
|
|
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
|
|
NEXT_PUBLIC_GITHUB_ENABLED: ${NEXT_PUBLIC_GITHUB_ENABLED}
|
|
DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID}
|
|
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET}
|
|
NEXT_PUBLIC_DISCORD_ENABLED: ${NEXT_PUBLIC_DISCORD_ENABLED}
|
|
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID}
|
|
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET}
|
|
AUTHENTIK_BASE_URL: ${AUTHENTIK_BASE_URL}
|
|
NEXT_PUBLIC_AUTHENTIK_ENABLED: ${NEXT_PUBLIC_AUTHENTIK_ENABLED}
|
|
SMTP_HOST: ${SMTP_HOST}
|
|
SMTP_PORT: ${SMTP_PORT}
|
|
SMTP_SECURE: ${SMTP_SECURE}
|
|
SMTP_USER: ${SMTP_USER}
|
|
SMTP_PASS: ${SMTP_PASS}
|
|
SMTP_FROM: ${SMTP_FROM}
|
|
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
|
|
CRON_SECRET: ${CRON_SECRET}
|
|
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
|
|
OPENROUTER_DEFAULT_MODEL: ${OPENROUTER_DEFAULT_MODEL}
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
|
|
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL}
|
|
ports:
|
|
# published on portainer LXC host; traefik (other LXC) routes here via <this-lxc-ip>:${WEB_PORT}
|
|
- "${WEB_PORT:-3000}:3000"
|
|
depends_on:
|
|
migrate:
|
|
condition: service_completed_successfully
|
|
redis:
|
|
condition: service_started
|
|
minio:
|
|
condition: service_healthy
|
|
minio-init:
|
|
condition: service_completed_successfully
|
|
|
|
# Fires the weekly digest email send by POSTing to `web`'s internal cron
|
|
# route on a schedule (see cron/crontab). Tiny alpine+curl+crond
|
|
# image — not part of the app build, just a periodic HTTP trigger.
|
|
digest-cron:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
target: cron
|
|
restart: always
|
|
environment:
|
|
CRON_SECRET: ${CRON_SECRET}
|
|
WEB_INTERNAL_URL: http://web:3000
|
|
depends_on:
|
|
- web
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
minio_data:
|