Security Scan (SIAX Cloud) / security (push) Successful in 17s
CI (SIAX Cloud) / security (push) Successful in 26s
CI (SIAX Cloud) / integration (push) Successful in 30s
CI (SIAX Cloud) / quality (push) Successful in 37s
CI (SIAX Cloud) / security (pull_request) Successful in 15s
CI (SIAX Cloud) / integration (pull_request) Successful in 25s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 26s
CI (SIAX Cloud) / quality (pull_request) Successful in 50s
0pt.app svarar i dag HTTP 402 DEPLOYMENT_DISABLED (Vercels betalningsspärr) och
har ingen självhostad ersättare igång. Det här gör appen faktiskt byggbar och
körbar på Coolify. Referens: sax3l/siax-io, som redan kör självhostat på flottan
— dess Dockerfile återanvänds i form i stället för att uppfinna ett eget mönster.
next.config.mjs:
* output: "standalone" — runtime-imagen kör `node server.js` i stället för att
släpa med pnpm, corepack och hela node_modules-trädet.
* images.unoptimized: true BORTTAGET. Det var v0-generatorns standardvärde, inte
ett val: next.config.mjs var orörd sedan "Initial commit from v0". Självhostat
är rätt lösning att låta Next optimera själv.
* poweredByHeader: false.
Dockerfile (fyra steg, samma form som siax-io):
* node:24-alpine -> node:22-alpine. Båda CI-workflowsen bygger på node 22;
att bygga och köra på samma major som grinden testar tar bort en version-skew.
* libc6-compat (sharp), icke-root `node`, HEALTHCHECK mot /api/health,
npm/npx strippat ur runtime-imagen (SIO-019-mönstret).
* --ignore-workspace borttaget: motiveringen i kommentaren var inaktuell sedan
P1-21 (pnpm-workspace.yaml har ett giltigt packages:-fält och
packages/schemas är en riktig medlem). Containerinstallationen blir därmed
bit för bit samma kommando som CI kör.
* --prod=false: Coolify injicerar NODE_ENV=production vid byggtid, och pnpm
hoppar då över devDependencies som next build kräver.
Sharp levereras i ett eget npm-steg, och det är inte godtyckligt:
next build spårar INTE sharp in i .next/standalone (verifierat: standalone-
utdatans node_modules innehöll bara next/, react/, react-dom/). Den uppenbara
lösningen outputFileTracingIncludes fungerar bevisligen inte — globben
"./node_modules/@img/**/*" får Turbopack att panika på pnpms katalogsymlänkar
("Is a directory (os error 21)") och bygget faller med exit 1. Reproducerat, inte
antaget. npm-steget lägger ett platt, symlänkfritt träd som runtime-steget kan
kopiera rakt av, pinnat till versionen i pnpm-lock.yaml, och faller hårt om
versionen inte går att läsa ut i stället för att tyst skeppa trasig optimering.
BEVISAT, inte påstått (fullständiga exitkoder i PR-beskrivningen):
* pnpm install --frozen-lockfile -> 0; next build med HELT TOM env -> 0
* docker build --no-cache -> 0; container startar, /api/health -> HTTP 200
* /api/ready utan DATABASE_URL -> HTTP 503 (faller stängt, som avsett)
* /api/0pt/* utan Bearer -> HTTP 401 (middleware kör självhostat)
* /_next/image -> HTTP 200 image/avif, 1 488 706 B PNG -> 51 161 B (29x)
* sharp laddar i runtime-imagen (libvips 8.18.6, musl-binärer)
* icke-root uid=1000, HEALTHCHECK "healthy", image 1,33 GB -> 423 MB
* typecheck 0, test 0 (930 passerade). lint/prettier faller identiskt på ren
main => pre-existerande, inte infört här. Ingen grind har rörts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
162 lines
8.3 KiB
Docker
162 lines
8.3 KiB
Docker
# =============================================================================
|
|
# 0pt web console — Dockerfile för Coolify (SIAX Cloud)
|
|
# =============================================================================
|
|
# Referensimplementation: sax3l/siax-io, som redan körs självhostat på flottan
|
|
# (Hetzner + Coolify + Traefik). Samma form återanvänds här i stället för att
|
|
# uppfinna ett eget mönster: flerstegsbygge, node:22-alpine, libc6-compat för
|
|
# sharp, icke-root, HEALTHCHECK, och npm/npx borttaget ur runtime-imagen.
|
|
#
|
|
# Den deployerbara tjänsten i det här repot är Next.js 16-appen i repo-roten.
|
|
# Liveness: GET /api/health (svarar alltid 200 så länge processen lever).
|
|
# Readiness: GET /api/ready (503 tills DATABASE_URL pekar på en nåbar Postgres)
|
|
# — peka Coolifys readiness-probe dit, INTE HEALTHCHECK nedan.
|
|
#
|
|
# VAD SOM ÄNDRADES MOT FÖREGÅENDE VERSION AV DEN HÄR FILEN, och varför:
|
|
#
|
|
# 1. next.config.mjs sätter nu `output: "standalone"`. Runtime-steget bär
|
|
# därför .next/standalone (server.js + spårade moduler) i stället för hela
|
|
# node_modules. Mätt på det här repot: den gamla imagen var 1,33 GB på disk
|
|
# / 315 MB komprimerat innehåll.
|
|
# 2. node:24-alpine -> node:22-alpine. Båda CI-workflowsen (.gitea/ och
|
|
# .github/workflows/ci.yml) kör `next build` på node 22; siax-io kör 22 i
|
|
# produktion. Att bygga och köra på samma major som grinden testar tar bort
|
|
# en version-skew utan uppsida.
|
|
# 3. `--ignore-workspace` borttaget. Kommentaren som motiverade flaggan
|
|
# ("pnpm-workspace.yaml ... carries only an `allowBuilds:` block and NO
|
|
# `packages:` field") är inaktuell: filen har ett giltigt `packages:`-fält
|
|
# och packages/schemas är en riktig workspace-medlem med egen package.json
|
|
# sedan BACKLOG P1-21. Utan flaggan blir installationen här BIT FÖR BIT
|
|
# samma kommando som CI kör (`pnpm install --frozen-lockfile`).
|
|
# 4. `--prod=false` tillagt. Coolify injicerar NODE_ENV=production vid byggtid,
|
|
# vilket får pnpm att hoppa över devDependencies (typescript, tailwindcss)
|
|
# som `next build` kräver. Samma fallgrop dokumenterad i siax-io:s
|
|
# Dockerfile för npm's `--include=dev`.
|
|
# 5. Eget `sharp`-steg, se nedan.
|
|
# 6. npm/npx strippat ur runtime-imagen (SIO-019-mönstret från siax-io).
|
|
# =============================================================================
|
|
|
|
# ---- beroenden ---------------------------------------------------------------
|
|
FROM node:22-alpine AS deps
|
|
WORKDIR /app
|
|
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
|
|
# sharps förbyggda binär och libvips kräver libc-kompatibilitet på Alpine.
|
|
RUN apk add --no-cache libc6-compat
|
|
|
|
RUN corepack enable && corepack prepare pnpm@9 --activate
|
|
|
|
# Manifesten först, för lagercache. pnpm-workspace.yaml OCH
|
|
# packages/schemas/package.json måste med: pnpm-lock.yaml har två importers
|
|
# (`.` och `packages/schemas`), och --frozen-lockfile vägrar om medlemmens
|
|
# manifest saknas på disk.
|
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
|
COPY packages/schemas/package.json ./packages/schemas/package.json
|
|
|
|
# --prod=false: se punkt 4 ovan. Utan den dör `next build` på saknad typescript
|
|
# så fort byggmiljön råkar ha NODE_ENV=production satt.
|
|
RUN pnpm install --frozen-lockfile --prod=false
|
|
|
|
# ---- sharp (bildoptimering) --------------------------------------------------
|
|
# Nexts bildoptimerare laddar `sharp` med en runtime-`require`, så `next build`
|
|
# spårar den INTE in i .next/standalone. Verifierat i det här repot: ett bygge
|
|
# med enbart `output: "standalone"` gav ett .next/standalone/node_modules som
|
|
# bara innehöll next/, react/ och react-dom/.
|
|
#
|
|
# `outputFileTracingIncludes` går inte att använda som lösning: globben
|
|
# "./node_modules/@img/**/*" får Turbopack att panika på pnpms katalogsymlänkar
|
|
# i node_modules/.pnpm ("Is a directory (os error 21)") och bygget faller med
|
|
# exit 1. Reproducerat, inte antaget.
|
|
#
|
|
# Därför ett eget steg med npm i stället för pnpm: npm lägger ett PLATT,
|
|
# symlänkfritt node_modules, vilket är precis vad runtime-steget kan kopiera rakt
|
|
# av. Versionen läses ur pnpm-lock.yaml så den aldrig kan glida ifrån vad resten
|
|
# av bygget löste ut, och steget faller hårt om utläsningen inte ger något.
|
|
# Eftersom det körs i en Alpine-image blir det musl-binärerna (@img/*-linuxmusl-*)
|
|
# som hamnar där, inte värdmaskinens.
|
|
FROM node:22-alpine AS sharp
|
|
WORKDIR /sharp
|
|
RUN apk add --no-cache libc6-compat
|
|
COPY pnpm-lock.yaml ./
|
|
RUN set -eu; \
|
|
SHARP_VERSION="$(sed -n 's/^ sharp@\([0-9][^(:]*\).*/\1/p' pnpm-lock.yaml | sort -u | head -1)"; \
|
|
if [ -z "$SHARP_VERSION" ]; then \
|
|
echo "FEL: hittade ingen sharp-version i pnpm-lock.yaml — vägrar bygga en image vars bildoptimering tyst skulle vara trasig." >&2; \
|
|
exit 1; \
|
|
fi; \
|
|
echo "sharp pinnad från pnpm-lock.yaml: ${SHARP_VERSION}"; \
|
|
npm install --no-save --no-audit --no-fund "sharp@${SHARP_VERSION}"; \
|
|
node -e "require('sharp'); console.log('sharp laddar OK i byggsteget')"
|
|
|
|
# ---- bygge -------------------------------------------------------------------
|
|
FROM node:22-alpine AS build
|
|
WORKDIR /app
|
|
|
|
RUN apk add --no-cache libc6-compat
|
|
RUN corepack enable && corepack prepare pnpm@9 --activate
|
|
|
|
# Endast rot-projektets node_modules kopieras. packages/schemas har noll
|
|
# beroenden (den paketerar bara JSON Schema-filer som konsumeras via sökväg),
|
|
# så pnpm skapar aldrig något packages/schemas/node_modules att kopiera —
|
|
# verifierat med `ls` efter en frozen install.
|
|
COPY --from=deps /app/node_modules ./node_modules
|
|
COPY . .
|
|
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
# Måste vara production. devDependencies behövs inte som NODE_ENV här — de ligger
|
|
# redan i node_modules från deps-steget. NODE_ENV=development skulle dessutom
|
|
# dra in Reacts utvecklingsruntime under prerendering (se siax-io:s Dockerfile,
|
|
# där det är uppmätt åt båda hållen).
|
|
ENV NODE_ENV=production
|
|
# Bygget får inte kräva riktiga secrets: lib/db.ts och lib/stripe.ts är lata och
|
|
# env-skyddade, och middleware.ts:s startgrind (assertAuthConfigSafe) klagar bara
|
|
# om AUTH_ENFORCE uttryckligen sätts till "false" i produktion. Verifierat: hela
|
|
# bygget går igenom utan en enda env-variabel satt.
|
|
RUN pnpm run build
|
|
|
|
# ---- runtime -----------------------------------------------------------------
|
|
FROM node:22-alpine AS runtime
|
|
WORKDIR /app
|
|
|
|
# `apk upgrade` hämtar Alpines senaste patchade paket (t.ex. libssl3/libcrypto3 —
|
|
# bastaggen släpar efter uppströms säkerhetsfixar) utan att röra den pinnade
|
|
# Node-versionen. wget behövs för HEALTHCHECK: bara node:*-alpine har varken
|
|
# curl eller wget.
|
|
RUN apk add --no-cache libc6-compat wget && apk upgrade --no-cache
|
|
|
|
ENV NODE_ENV=production
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
ENV PORT=3000
|
|
ENV HOSTNAME=0.0.0.0
|
|
|
|
# standalone-utdatan kopierar varken public/ eller .next/static — de måste med
|
|
# explicit, annars serveras appen utan statiska assets.
|
|
COPY --from=build --chown=node:node /app/public ./public
|
|
COPY --from=build --chown=node:node /app/.next/standalone ./
|
|
COPY --from=build --chown=node:node /app/.next/static ./.next/static
|
|
|
|
# sharp läggs BREDVID standalone-utdatans egna node_modules (next/react/react-dom).
|
|
# Docker slår ihop katalogerna, så inget skrivs över. `require('sharp')` från
|
|
# next/dist/... löses ut genom att vandra uppåt till /app/node_modules/sharp.
|
|
COPY --from=sharp --chown=node:node /sharp/node_modules ./node_modules
|
|
|
|
# Bas-imagen node:22-alpine skeppar npms egen globala installation
|
|
# (/usr/local/lib/node_modules/npm) med ett eget beroendeträd som regelbundet
|
|
# bär kända HIGH/CRITICAL-CVE:er, helt utan koppling till den här appens kod.
|
|
# Runtime-steget kör `node server.js` — varken RUN, CMD eller HEALTHCHECK här
|
|
# anropar npm eller npx — så trädet kan tas bort i stället för att accepteras.
|
|
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx /usr/local/share/man/man1/npm*
|
|
|
|
USER node
|
|
|
|
EXPOSE 3000
|
|
|
|
# LIVENESS. /api/health svarar 200 så länge processen kan svara — rätt signal för
|
|
# "starta om containern". Peka INTE hit en readiness-probe: /api/ready svarar 503
|
|
# tills DATABASE_URL är satt och Postgres är nåbar, och en HEALTHCHECK mot den
|
|
# skulle få Coolify att döda en container som fungerar precis som avsett.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=25s --retries=3 \
|
|
CMD wget -qO- http://127.0.0.1:3000/api/health >/dev/null 2>&1 || exit 1
|
|
|
|
CMD ["node", "server.js"]
|