Lille CI, stor forskel
∙ Du laver commit, pusher, og GitHub lyser rødt uden du aner hvorfor
∙ Din Node-app bygger fint lokalt, men Actions siger “command not found”
∙ Tests kører hurtigt hos dig, men hænger i CI
∙ Du har allerede googlet “github actions node fails locally works” mindst én gang
Hvis du nikkede til mindst et punkt, så er det her din “minimum CI” opsætning med GitHub Actions, uden magi og uden 400 linjer YAML.
Minimum CI vs “enterprise CI”
Der er to verdener:
- Lokalt: din maskine, din Node-version, dine globale pakker, din tilfældige rækkefølge af kommandoer.
- CI: ren maskine hver gang, alt skal beskrives, ingen gætterier.
Minimum CI er ikke alt det fancy med deploy, docker, matrix builds og 7 miljøer. Minimum er:
- Installer afhængigheder
- Lint koden
- Kør tests
- Byg projektet (hvis der er build-step)
Det er det. Ingen deploy, ingen releases, ingen ekstra filer. Bare en robot der kører de samme kommandoer som du bør køre lokalt, hver gang du pusher.
Hvad du ikke behøver endnu
Hvis du er i gang med små Node-projekter, måske en Vite-frontend eller en simpel backend fra vores kategori om backend til web, så spring trygt over:
- Deploy til server eller cloud fra Actions
- Docker-builds i CI
- Kompleks caching med flere nøgler
- 10 forskellige workflows til hver gren
Det kan komme senere. Først vil du have noget der giver dig et ærligt “grønt eller rødt” svar på: kan det her projekt køres af en maskine der ikke kender dig.
Før vs nu: dit lokale workflow og CI
Lad os sammenligne en typisk junior-workflow med den lille, sunde CI-version. Det her er den røde tråd i resten af artiklen.
| Lokalt nu | CI med minimum workflow |
|---|---|
| Du åbner projektet, kører “det plejer at virke”-kommandoer i vilkårlig rækkefølge. | Actions kører altid: npm install → npm run lint → npm test → npm run build. |
| Du bruger en Node-version du engang installerede. | Workflowet sætter eksplicit Node-version via setup-node. |
| Du har måske globale værktøjer installeret (jest, vite, eslint). | Alt skal findes i package.json scripts, ellers fejler det. |
| Fejl kan gemme sig i cache, gammel build, mærkelig state. | Ren maskine hver gang. Hvis noget kun virker “med held”, bliver det afsløret. |
Målet er at de to kolonner bliver så ens som muligt. Samme Node-version, samme scripts, samme forventninger.
Et simpelt GitHub Actions workflow til Node
Her er en helt minimal YAML der giver dig checkout → setup node → install → lint → test → build.
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout kode
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Installer afhængigheder
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build
Forudsætninger i dit projekt
For at det her ikke eksploderer på første push, så tjek:
- Du har en
.nvmrcmed en Node-version, fx18.19.0 - Du bruger npm og har en
package-lock.json - Du har scripts i
package.json:
{
"scripts": {
"lint": "eslint .",
"test": "vitest run",
"build": "vite build"
}
}
Hvis du ikke har lint eller tests endnu, så kan du starte med kun install + build, og tilføje resten senere. Men strukturen er den samme.
Node-version og lockfile: kilden til “virker lokalt”
Langt de fleste “GitHub Actions fejler men lokalt er alt fint”-problemer kommer fra to ting:
- Forskellig Node-version
- Rod i dine lockfiler
Sådan gør du Node-versionen ens
Lav en .nvmrc i roden af projektet:
18.19.0
Brug så nvm lokalt:
nvm use
node -v # tjek at den matcher
I workflowet bruger vi node-version-file: '.nvmrc'. Det betyder at Actions læser den samme fil. Ikke noget med “vi har cirka samme version”. Præcis den samme.
Lockfile disciplin
Hvis du bruger npm, så:
- Commit
package-lock.json - Brug
npm cii CI, ikkenpm install
npm ci er streng: hvis din package.json og package-lock.json ikke passer sammen, fejler den. Det er præcis det du vil have. Lokalt opdager du måske aldrig mismatch, fordi du bare kører npm install og npm “retter” dine afhængigheder.
Typisk fejlscenario
Symptom: Actions fejler på “Cannot find module X”, lokalt virker det.
Årsag: Du har installeret et ekstra modul uden at commite lockfilen, eller du har rodet i node_modules manuelt.
Fix:
- Slet
node_moduleslokalt - Kør
npm cilokalt - Ret eventuelle versioner, commit
package-lock.json - Push igen
Caching: hjælp vs. hovedpine
Vi brugte lige:
with:
node-version-file: '.nvmrc'
cache: 'npm'
Det er en simpel cache af npm-dependencies baseret på din lockfil. Det kan spare 10-30 sekunder, nogle gange mere, men vigtigere: det ejer du ikke selv. GitHub håndterer det for dig.
Når cache er en god idé
Brug den indbyggede cache når:
- Du har én package manager (npm)
- Du ikke selv opfinder nøgler og paths
- Du er ok med at slette cachen ved at ændre lockfilen
Når cache skaber fejl
Fejl opstår typisk når man begynder at lave “kreativ” caching:
- Du cacher hele
node_modulesmanuelt med en for bred nøgle - Du ændrer Node-version uden at ændre cachen
- Du har skiftet fra npm til pnpm eller yarn, men cachen ligger der stadig
Hvis du ser mærkelige fejl der forsvinder når du ændrer en tilfældig ting, så prøv at slå cache fra midlertidigt. CI skal være deterministisk. Ingen “det virkede da jeg ændrede et mellemrum i YAML”.
Secrets og miljøvariabler i CI
På et tidspunkt skal du bruge miljøvariabler i dine tests eller build: API-nøgler, base URLs, osv. I Actions bruger du Secrets og env.
Sådan sætter du en secret
- Gå til dit repo på GitHub
- Settings → Secrets and variables → Actions → New repository secret
- Kald den fx
API_URL, værdi fxhttps://api.example.com
I workflowet kan du så gøre:
jobs:
build-and-test:
runs-on: ubuntu-latest
env:
API_URL: ${{ secrets.API_URL }}
Nu ser din Node-app bare en helt normal process.env.API_URL i CI.
Hvis du vil nørde mere i miljøvariabler generelt, så ligger der meget god baggrund i vores tag om miljøvariabler.
Typiske fejl med secrets i Actions
- Du skriver secret-navnet forkert i YAML
- Du prøver at echo hele secreten ud i loggen (det bliver maskeret, heldigvis)
- Du har et
.envlokalt, men har aldrig sat secrets i GitHub
Mini-tjek: alt hvad du har i din lokale .env som projektet faktisk behøver for at køre tests/build, skal også findes som secret eller env i CI.
Flaky tests: hvorfor de dør i CI først
Flaky tests er tests der fejler nogle gange. De er irriterende lokalt. I CI er de direkte skadelige, for så lærer du at ignorere røde builds.
Fem klassiske flaky-årsager
- Testen afhænger af tid
Du brugersetTimeouteller forventer noget inden for X millisekunder. På en travl CI-maskine kan det være for langsomt. - Global state genbruges
Tests deler globale variabler, database state eller filer. - Netværkskald uden mock
Tests ringer ud i verden. Nogen gange er nettet langsomt, API nede, osv. - Order dependency
Test B virker kun hvis test A kørte først. - Randomness uden kontrol
Du brugerMath.random()uden at styre outputtet i testen.
Hurtige fixes
- Brug fake timers eller mock tid hvor det giver mening
- Ryd op i global state i
beforeEach/afterEach - Mock netværkskald i stedet for at ramme rigtige endpoints
- Sørg for at hver test kan køre alene
- Kontroller randomness ved at mocke eller isolere den del
Hvis du vil have lidt flere vinkler på test-setup i frontend-land, så har jeg skrevet om “test uden tårer i Vite projekter” før. Det spiller ret godt sammen med det her Actions-setup.
PR checks og branch protection: solo vs team
Der er lidt forskel på om du sidder alene på et projekt, eller om du er flere.
Solo-projekt
Mit forslag:
- Lad workflowet køre på både
pushogpull_request - Du kan merge selv om CI er rødt, men gør det bevidst (og fiks det hurtigt)
- Brug Actions mest som ekstra sikkerhed og vane-træner
Lille team eller studiegruppe
Her begynder CI for alvor at gøre en forskel, sammen med de ting vi også er inde på under samarbejde og workflows.
Et simpelt setup:
- Sæt branch protection på
main:
- Repo → Settings → Branches → Add rule
- Vælg branch:
main - Slå “Require status checks to pass before merging” til
- Vælg dit CI workflow som krævet check
Nu kan ingen merge til main uden grønt CI. Det virker måske lidt strengt, men det sparer jer for “hov, jeg glemte lige at køre tests” 3 dage før deadline.
Skabelon: minimum Node-workflow linje for linje
Her er en lille “copy-adapt” version med kommentarer. Kopier den, tilpas navnene og fjern kommentarerne.
name: Node CI
on:
push:
branches: [ main ] # kør når der pushes til main
pull_request:
branches: [ main ] # og når der laves PRs mod main
jobs:
build-and-test:
runs-on: ubuntu-latest # GitHub hostet Linux-maskine
steps:
- name: Checkout kode
uses: actions/checkout@v4 # henter dit repo ned på runneren
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc' # brug samme version som lokalt
cache: 'npm' # enkel cache baseret på lockfile
- name: Installer afhængigheder
run: npm ci # brug lockfile præcist
- name: Lint
run: npm run lint # kør din lint-kommando
- name: Test
run: npm test # eller npm run test, alt efter script
- name: Build
run: npm run build # byg projektet (drop step hvis du ingen build har)
Mini-øvelse: gør lokalt og CI ens
Prøv det her i et eksisterende projekt:
- Lav
.nvmrchvis du ikke har den - Sørg for at
npm cikan køre lokalt uden fejl - Tjek at
npm run lint,npm testognpm run buildalle virker lokalt - Tilføj workflow-filen ovenfor som
.github/workflows/ci.yml - Commit og push
- Åbn Actions-tabben og læs loggen roligt igennem, især første gang den fejler
Hvis du allerede har øvet dig på at læse logs i andre sammenhænge, fx via vores kategori om fejlfinding og debugging, så føles Actions hurtigt som “endnu et sted hvor maskinen fortæller dig hvad der sker” og ikke som en fjende.
Min egen lille holdning til CI for begyndere
Jeg synes faktisk minimum CI er en af de bedste måder at blive tvunget til at tage din egen kode seriøst, længe før du får dit første udviklerjob. En grøn Actions-historik på et lille projekt siger mere om dine vaner end endnu en to-do app uden tests nogensinde kommer til.









Send kommentar
Du skal være logget ind for at skrive en kommentar.