Hopp til hovedinnhold
nextjspnpmturborepomonorepocoolify· 13. mai 2026 · 3 min

Når Coolify ikke bygger pakkene CI bygger

Coolify bygger en pnpm-monorepo med pnpm filter, ikke med Turborepo. Når interne pakker eksporterer fra dist/, går prod-bygget i bakken mens CI er grønn

Når Coolify ikke bygger pakkene CI bygger

Forrige uke pushet jeg en feature med Vipps-integrasjon til stage. CI ble grønn, alle tester passerte. Deploy-en til Coolify feilet med en feilmelding jeg ikke kjente igjen: webpack klagde over at den ikke fant @flowment/payments. Pakken finnes. Den eksporterer det jeg importerer. Den bygges i CI uten støy. Likevel, i Docker-bygget i Coolify, fantes den ikke.

Det viste seg å være en forskjell jeg ikke hadde tenkt på mellom hvordan Turborepo bygger lokalt og hvordan Coolify bygger i prod. Den forskjellen er ikke åpenbar, og den lurte meg i to timer før jeg så hva som skjedde.

To forskjellige bygg

Lokalt og i GitHub Actions kjører jeg pnpm turbo run build. Turbo leser turbo.json, ser at @flowment/web har "dependsOn": ["^build"], og bygger alle upstream-pakker først. @flowment/payments, @flowment/domain og @flowment/db får alle kjørt sin egen build-script, som tsc-er TypeScript ned til dist/. Når Next.js 15 så starter sitt bygg, finner den @flowment/payments/dist/index.js der den forventer.

Coolifys standardoppsett for en pnpm-monorepo har en annen tilnærming. Dockerfilen som genereres ender på:

RUN pnpm install --frozen-lockfile
RUN pnpm --filter @flowment/web build

pnpm --filter kjører kun build-scriptet for @flowment/web. Den bygger ikke pakkene web er avhengig av. pnpm har ingen oppfatning av at @flowment/payments må bygges først, det er en konvensjon Turborepo eier. Resultatet er at Next.js starter sitt bygg, og alle dist-mappene i workspace-pakkene er tomme.

Feilen fra webpack ble til slutt:

Module not found: Can't resolve '@flowment/payments'

Import trace for requested module:
./app/checkout/actions.ts

Pakken er installert. Symlinken finnes i node_modules. Men package.json peker mot noe som ikke eksisterer:

{
  "name": "@flowment/payments",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  }
}

Det åpenbare valget jeg ikke tok

Den umiddelbare løsningen er å bygge pakkene eksplisitt i Dockerfilen før Next.js:

RUN pnpm -r --filter "./packages/*" build
RUN pnpm --filter @flowment/web build

Det fungerer. Det er også feil sted å løse problemet. Hver gang Coolify regenererer Dockerfilen etter en Nixpacks-oppdatering, eller hvis jeg flytter prosjektet til en annen plattform, forsvinner den linjen. Bygget blir avhengig av at jeg husker en spesifikk infrastruktur-detalj.

Det jeg endte på, er å fjerne tsc-bygge-steget for interne pakker helt. Next.js 15 transpilerer arbeids-pakker hvis du sier ifra:

const nextConfig: NextConfig = {
  transpilePackages: [
    "@flowment/payments",
    "@flowment/domain",
    "@flowment/db",
  ],
};

Og pakkenes package.json peker rett mot TypeScript-kilden:

{
  "name": "@flowment/payments",
  "exports": {
    ".": {
      "types": "./src/index.ts",
      "import": "./src/index.ts"
    }
  }
}

Ingen dist/, ingen tsc-steg, ingen avhengighet på byggerekkefølge. Next.js leser TypeScript-en direkte fra pakkens kildemappe og kjører den gjennom sin egen SWC-pipeline. For pakker som konsumeres av Trigger.dev-runneren i samme monorepo gjør jeg det samme, og lar bundleren der ta seg av transpilering.

Det som skurer

Editor-støtten er fortsatt litt rar. TypeScript-prosjektreferanser fungerer, men typegen-pakken for Supabase forventer at den finner kompilerte .d.ts-filer. Det måtte løses pakke for pakke ved å beholde en minimal tsc-emit for types-feltet, mens import-conditionen peker mot kilden:

{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./src/index.ts"
    }
  }
}

Det skurer mot prinsippet om at filer på disk skal være ekte filer, ikke valgfrie artefakter. Hvis jeg gjorde det på nytt, ville jeg vurdert å droppe exports-feltet helt og bruke main mot src/index.ts. Eller bare akseptere et byggesteg i prod og kjøre tsup --watch i dev. Begge er avveininger, ingen er åpenbart riktige.

Det egentlige problemet

CI som bygger via Turborepo, og prod som bygger via pnpm --filter, er ikke det samme bygget. Hvis de skiller seg, er det CI som lyver. Jeg har lagt til en separat GitHub Actions-jobb som bygger med pnpm --filter @flowment/web build uten Turbo-cache, så differansen mellom oppsettene fanges før den treffer Coolify. Den ene jobben er en kopi av Coolifys Dockerfile-steg, ingenting annet.

Det burde vært den første jeg satt opp. Hver gang noe står på «deploy bare i prod»-listen min, har det vist seg at CI bygger med en assumption som ikke holder i prod. Det er ikke et Coolify-problem, det er et generelt monorepo-problem. Det neste deploy-mønsteret jeg flytter til (Railway, Fly, Render) kommer til å ha en annen variant av det samme.

Hvis du likte dette

Abonnér via RSS eller JSON Feed, eller ta kontakt.

Del på: LinkedIn · Redi AS · Spotify · Instagram · Wikidata