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).
81 lines
3.5 KiB
Docker
81 lines
3.5 KiB
Docker
# syntax=docker/dockerfile:1
|
|
FROM node:22-alpine AS base
|
|
RUN corepack enable
|
|
|
|
# ---- deps: install full workspace deps ----
|
|
FROM base AS deps
|
|
WORKDIR /repo
|
|
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
|
COPY apps/web/package.json apps/web/package.json
|
|
COPY packages/db/package.json packages/db/package.json
|
|
RUN pnpm install --frozen-lockfile
|
|
|
|
# ---- migrator: applies drizzle migrations + seeds tier definitions ----
|
|
FROM deps AS migrator
|
|
WORKDIR /repo/packages/db
|
|
COPY packages/db .
|
|
CMD ["sh", "-c", "pnpm migrate && pnpm seed"]
|
|
|
|
# ---- build ----
|
|
FROM base AS build
|
|
WORKDIR /repo
|
|
COPY --from=deps /repo/node_modules ./node_modules
|
|
COPY --from=deps /repo/apps/web/node_modules ./apps/web/node_modules
|
|
COPY --from=deps /repo/packages/db/node_modules ./packages/db/node_modules
|
|
COPY . .
|
|
# apps/web/.env.local is normally a symlink to repo-root .env.local (gitignored);
|
|
# it doesn't exist in the build context, so Next's env loader chokes on the dangling
|
|
# link. No secrets are needed at build time — they're injected at container runtime.
|
|
RUN rm -f apps/web/.env.local && touch apps/web/.env.local
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
# NEXT_PUBLIC_* vars are inlined into the client bundle at build time, not read at
|
|
# container runtime — must be passed as build args (compose.prod.yml wires these
|
|
# from the same stack env vars used at runtime, so they stay in sync).
|
|
ARG NEXT_PUBLIC_VAPID_PUBLIC_KEY
|
|
ARG NEXT_PUBLIC_GITHUB_ENABLED
|
|
ARG NEXT_PUBLIC_DISCORD_ENABLED
|
|
ARG NEXT_PUBLIC_AUTHENTIK_ENABLED
|
|
ENV NEXT_PUBLIC_VAPID_PUBLIC_KEY=$NEXT_PUBLIC_VAPID_PUBLIC_KEY
|
|
ENV NEXT_PUBLIC_GITHUB_ENABLED=$NEXT_PUBLIC_GITHUB_ENABLED
|
|
ENV NEXT_PUBLIC_DISCORD_ENABLED=$NEXT_PUBLIC_DISCORD_ENABLED
|
|
ENV NEXT_PUBLIC_AUTHENTIK_ENABLED=$NEXT_PUBLIC_AUTHENTIK_ENABLED
|
|
# next.config.ts reads STORAGE_PUBLIC_URL to compute the CSP connect-src/img-src
|
|
# allowlist, and next.config.ts's headers() are evaluated at build time — so this
|
|
# also needs to be a build arg, not just a runtime env var (unlike STORAGE_ENDPOINT).
|
|
ARG STORAGE_PUBLIC_URL
|
|
ENV STORAGE_PUBLIC_URL=$STORAGE_PUBLIC_URL
|
|
# lib/storage.ts's getPublicUrl() is called from client components to render photo
|
|
# <img> tags — a plain (non-NEXT_PUBLIC_) env var is never inlined into the browser
|
|
# bundle, so it must be duplicated under a NEXT_PUBLIC_ name to reach the client.
|
|
ARG NEXT_PUBLIC_STORAGE_PUBLIC_URL
|
|
ENV NEXT_PUBLIC_STORAGE_PUBLIC_URL=$NEXT_PUBLIC_STORAGE_PUBLIC_URL
|
|
RUN pnpm --filter web build
|
|
|
|
# ---- runtime ----
|
|
FROM base AS runner
|
|
WORKDIR /app
|
|
ENV NODE_ENV=production
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
ENV PORT=3000
|
|
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
|
|
|
|
COPY --from=build /repo/apps/web/public ./public
|
|
COPY --from=build --chown=nextjs:nodejs /repo/apps/web/.next/standalone ./
|
|
COPY --from=build --chown=nextjs:nodejs /repo/apps/web/.next/static ./apps/web/.next/static
|
|
|
|
USER nextjs
|
|
EXPOSE 3000
|
|
CMD ["node", "apps/web/server.js"]
|
|
|
|
# ---- cron: periodically triggers internal cron endpoints (e.g. weekly digest) ----
|
|
# Minimal alpine + busybox crond image — just curls the running `web` service on a
|
|
# schedule. Doesn't need the app build output at all, so it's based on `base`
|
|
# rather than `runner`, keeping the image tiny and independent of the Next build.
|
|
FROM base AS cron
|
|
RUN apk add --no-cache curl
|
|
COPY cron/crontab /etc/crontabs/root
|
|
COPY cron/run-digest.sh /usr/local/bin/run-digest.sh
|
|
COPY cron/run-leftover-reminders.sh /usr/local/bin/run-leftover-reminders.sh
|
|
RUN chmod +x /usr/local/bin/run-digest.sh /usr/local/bin/run-leftover-reminders.sh
|
|
CMD ["crond", "-f", "-l", "2"]
|