Files
0pt/Dockerfile
T
Simon AxelssonandClaude Opus 5 d428dd5018
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
feat(deploy): gör 0pt körbar utanför Vercel (standalone + bevisad Docker-image)
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>
2026-09-07 18:50:14 +02:00

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"]