Jeg blev træt af at klikke “Deploy” – så lod jeg GitHub gøre det

Jeg blev træt af at klikke “Deploy” - så lod jeg GitHub gøre det

Jeg ville have en lille webapp til at deploye sig selv, hver gang jeg pushede til main. Ingen flere “hov, jeg glemte lige at bygge først”. I stedet endte jeg med et simpelt GitHub Actions-setup, der tester, bygger og deployer for mig på under et minut.

I den her tekst viser jeg dig præcis den pipeline jeg selv ville sætte op i dag, hvis jeg startede fra nul med GitHub Actions og CI/CD.

Hvad CI/CD faktisk gør for et lille projekt

Jeg starter lige med det, jeg selv skulle have forstået meget tidligere: CI/CD er ikke kun til kæmpe virksomheder.

Du får tre meget konkrete ting, også i dit lille hobbyprojekt:

  • Mindre rod: Samme kommandoer hver gang, på en ren maskine.
  • Mindre “virker kun hos mig”: Din kode skal virke på GitHub, før den kommer ud.
  • Mindre manuel klikfest: Du skubber kode, en robot gør resten.

CI betyder “Continuous Integration” – hver gang du pusher, bliver koden bygget og testet. CD kan være to ting: “Continuous Delivery” (klar til deploy) eller “Continuous Deployment” (faktisk deploy automatisk). Vi snupper en lille blanding: test + build hver gang, og automatisk deploy fra main.

Hvad du skal have klar før du overhovedet åbner GitHub Actions

Jeg antager, at du har en lille webapp. Det kan være en Vite/React, Next.js, Svelte eller bare noget bundet sammen med npm scripts.

Før du begynder på GitHub Actions, så tjek tre ting lokalt:

1. Et repo på GitHub

Din kode skal ligge i et GitHub repository. Ikke kun lokalt. Push alt op:

git init
git add .
git commit -m "første commit"
git branch -M main
git remote add origin git@github.com:dit-navn/dit-repo.git
git push -u origin main

Hvis du er i tvivl om git-delen, så er det bedre at få den på plads først. GitHub Actions lever og dør med dit repo.

2. En sikker build kommando

I din package.json skal du have noget i den her stil:

{
  "scripts": {
    "build": "vite build",
    "test": "vitest run"
  }
}

Kør lokalt:

npm install
npm run build

Hvis det fejler lokalt, skal du ikke forvente mirakler i CI. Fix det her først.

3. En test-kommando (også selvom den er lille)

Du kan sagtens starte uden tests, men du lærer mere af at have bare én test, der kan fejle.

Eksempel med vitest:

// sum.test.js
import { describe, it, expect } from 'vitest'
import { sum } from './sum'

describe('sum', () => {
  it('lægger to tal sammen', () => {
    expect(sum(2, 3)).toBe(5)
  })
})

Og i terminalen:

npm run test

Når både build og test virker lokalt, er du klar til at skubbe det over i GitHub Actions.

Din første GitHub Actions workflow der rent faktisk gør noget

Nu kommer det stykke, jeg selv brugte alt for meget tid på første gang: YAML-filen.

Vi laver en fil, der gør tre ting, hver gang du pusher til main:

  • Tjekker koden ud
  • Installerer dependencies (med cache)
  • Bygger projektet

Opret workflow-filen

Lav mappen og filen:

.github/
  workflows/
    ci.yml

Indhold:

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Tjek kode ud
        uses: actions/checkout@v4

      - name: Sæt Node.js version
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Installer dependencies
        run: npm ci

      - name: Byg projekt
        run: npm run build

Hvad sker der faktisk her?

Lad os tage den blok for blok.

name er bare navnet i GitHub Actions UI. Ren kosmetik.

on styrer, hvornår workflowet kører:

  • push > branches: [ main ] betyder: kør når der pushes til main.
  • pull_request > branches: [ main ] betyder: kør for PRs mod main.

jobs.build.runs-on er typen af virtuel maskine. ubuntu-latest er standarden og fin til stort set alt web.

steps er de enkelte kommandoer.

  • actions/checkout henter din kode ned på maskinen.
  • actions/setup-node vælger node-version og sætter npm-cache op.
  • npm ci installerer dependencies hurtigt og deterministisk.
  • npm run build bygger din app.

Commit filen, push til main og åbn fanen “Actions” på GitHub. Du skulle gerne se workflowet køre.

Typisk fejl i første forsøg

En af de klassiske:

  • Fejl: npm ci fejler fordi der ikke er en package-lock.json.
  • Løsning: kør npm install lokalt, commit package-lock.json, og push igen.

Hvis du bruger pnpm eller yarn, skal du skifte kommandoer og cache-type. GitHub har eksempler i deres dokumentation, men jeg vil anbefale at starte med npm, hvis du er helt ny.

Tilføj tests: lad workflowet fejle før du deployer

Når build kører stabilt, er næste trin bare at tilføje tests. Her bliver CI-delen rigtig nyttig.

Vi indfører en test-step før build:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Tjek kode ud
        uses: actions/checkout@v4

      - name: Sæt Node.js version
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Installer dependencies
        run: npm ci

      - name: Kør tests
        run: npm test

      - name: Byg projekt
        run: npm run build

Nu sker der en simpel men vigtig ting: hvis npm test fejler, bliver de næste steps aldrig kørt. Dit workflow stopper der.

Det betyder, at du kan lave en regel med dig selv: “Hvis Actions er rød, deployer jeg ikke”. I næste afsnit lader vi GitHub gøre deployet, så du heller ikke selv kan snige dig udenom.

Secrets i GitHub Actions uden at lække nøgler

Inden vi deployer til en ekstern host, bliver vi nødt til at tale om secrets. Det er ikke specielt spændende, men det er det sted, hvor mange kommer til at lægge nøgler direkte i YAML-filen.

Du må aldrig committe dine API-nøgler, deploy-tokens eller andre hemmeligheder. Brug i stedet GitHub Secrets.

Hvor ligger secrets i GitHub?

Gå til dit repo på GitHub:

  1. Settings
  2. Secrets and variables
  3. Actions
  4. “New repository secret”

Her kan du kalde en secret fx NETLIFY_AUTH_TOKEN og indsætte værdien.

I workflowet får du adgang via ${{ secrets.NETLIFY_AUTH_TOKEN }}.

Eksempel: brug af secret i en step

- name: Deploy til et eller andet
  run: some-cli deploy --token ${{ secrets.NETLIFY_AUTH_TOKEN }}

Den værdi bliver sat som en environment variabel, når step’et kører. GitHub forsøger også at maskere den i logs, hvis den skulle blive skrevet ud ved et uheld.

Hvis du på et tidspunkt vil arbejde videre med environment variables til selve din app (for eksempel API_URL), så har jeg også en artikel om env-vars og deploy, som du kan starte med: Hvis det kun virker på din maskine, virker det ikke.

Deploy-mønstre: vælg en host du ikke skal slås med

Nu har vi en CI-del. Den bygger og tester. Næste skridt er CD: faktisk at sende din buildede app ud.

Her er tre typiske mål for en simpel webapp:

  • Netlify: God til statiske sites og SPA.
  • Vercel: God til Next.js og moderne frontend.
  • GitHub Pages: Simpelt, gratis, men lidt mere manuelt.

Både Netlify og Vercel kan egentlig selv lave CI/CD, bare ved at koble dit GitHub repo direkte på. I mange små projekter er det rigeligt.

Men hvis du vil øve “rigtig” CI/CD, hvor GitHub Actions styrer hele forløbet, så kan du lade Actions stå for byg + deploy, og bruge Netlify/Vercel som ren hosting.

Deploy til Netlify med GitHub Actions

Det mest lige-ud-af-landevejen setup er ofte:

  1. Opret site i Netlify (via webinterfacet).
  2. Hent en “personal access token”.
  3. Angiv “site ID”.
  4. Brug netlify-cli i dit workflow.

Først installerer du netlify-cli som dev dependency:

npm install -D netlify-cli

Tilføj secrets i GitHub:

  • NETLIFY_AUTH_TOKEN
  • NETLIFY_SITE_ID

Så udvider vi workflowet:

name: CI/CD

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Tjek kode ud
        uses: actions/checkout@v4

      - name: Sæt Node.js version
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Installer dependencies
        run: npm ci

      - name: Kør tests
        run: npm test

      - name: Byg projekt
        run: npm run build

      - name: Deploy til Netlify
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        run: npx netlify deploy --dir=dist --prod

Her antager jeg, at dit build-output ligger i mappen dist. Hvis du bruger noget andet (fx build), så skift --dir=dist ud.

Netlify-dokumentationen har lidt mere om det her mønster, men i virkeligheden er det bare: bygg app, kør CLI med token.

Deploy til Vercel med GitHub Actions

Med Vercel er den normale måde at lade Vercel selv stå for hele CI/CD-processen, når du forbinder repoet. Men du kan godt styre det via GitHub Actions, hvis du vil have ét samlet flow.

Grundideen er den samme:

  • Få en Vercel token.
  • Sæt den som secret i GitHub.
  • Brug vercel CLI i en deploy-step.

Eksempel på en simpel step:

- name: Deploy til Vercel
  env:
    VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
  run: npx vercel --prod --token=$VERCEL_TOKEN

Her lader du typisk Vercel selv håndtere build. Hvis du vil bruge dit eget build-output, skal du ind og lege lidt mere med config. Hvis målet er at lære GitHub Actions, ville jeg personligt tage Netlify-varianten til at starte med.

Deploy til GitHub Pages

GitHub Pages er fin til simple statiske sites. Her kan du bruge en eksisterende Action i stedet for at skrive hele deploy-delen selv.

Hvis du allerede har en build-step, der laver en dist-mappe, kan du tilføje noget i stil med:

- name: Deploy til GitHub Pages
  uses: peaceiris/actions-gh-pages@v3
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: ./dist

Husk at slå GitHub Pages til under repoets Settings, og peg den på den branch, actionen bruger (typisk gh-pages).

Fejlsøgning: når din pipeline går i rødt midt om aftenen

Første gang din workflow bliver rød, er det fristende bare at skubbe endnu et commit og håbe. Jeg har prøvet. Det hjælper sjældent.

Jeg gør typisk det her, når noget fejler i Actions:

1. Læs hele loggen for det step der fejler

Klik ind på det job der er rødt. Fold step’et ud, der fejler. Scrol helt ned. Næsten alle fejl ender med en ret brugbar linje.

Eksempler jeg ofte ser:

  • Command 'npm ci' not found – du kører måske i et andet environment end forventet.
  • Cannot find module 'react' – manglende dependency eller fejl i lockfile.
  • ReferenceError: process is not defined – bundling-problem eller forkert environment.

2. Genskab fejlen lokalt

Hvis fejlen handler om build eller tests, så prøv at gøre det samme lokalt i et friskt miljø.

Jeg plejer at gøre noget i den her stil:

rm -rf node_modules
rm -rf dist
npm ci
npm test
npm run build

Hvis det fejler lokalt på samme måde, har du problemet tættere på fingrene.

3. Brug “Re-run jobs” når du retter workflow-filen

Du kan genkøre et job uden at pushe ny kode. På Actions-siden er der en “Re-run jobs” knap. Det er rart, når du kun har ændret på YAML-filen og vil se, om det nu kører igennem.

Pas dog på ikke at bruge det som “jeg håber det virker nu”-knap. Hvis du ikke har ændret noget relevant, vil den jo fejle på samme måde.

Nogle meget almindelige begynderslag i hovedet

  • Forkert node-version: Du bygger på Node 20 lokalt, men Actions kører Node 16. Løsning: sæt samme version i actions/setup-node som du bruger lokalt.
  • Manglende env vars i CI: Din app bruger process.env.API_URL, men du har ikke sat den i GitHub. Løsning: brug repository variables/secrets.
  • Sti til build-output: Deploy-step peger på dist, men din app bygger til build eller noget helt tredje. Løsning: tjek package.json eller bundler-config.

Hvis du rammer deploy-issues, der kun opstår i produktion, er det værd at kigge på den artikel om deploy-fejl og miljøforskelle: Jeg stoler mere på mine logs end på min hukommelse.

Et lille “best practice” workflow du kan kopiere fra projekt til projekt

Når jeg starter et nyt lille webprojekt, ender jeg ret hurtigt med noget i den her stil. Det er ikke perfekt, men det er simpelt nok til, at jeg gider vedligeholde det.

name: CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test-and-build:
    runs-on: ubuntu-latest

    steps:
      - name: Tjek kode ud
        uses: actions/checkout@v4

      - name: Sæt Node.js version
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Installer dependencies
        run: npm ci

      - name: Kør tests
        run: npm test

      - name: Byg projekt
        run: npm run build

  deploy:
    needs: test-and-build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'

    steps:
      - name: Tjek kode ud
        uses: actions/checkout@v4

      - name: Sæt Node.js version
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Installer dependencies
        run: npm ci

      - name: Byg projekt
        run: npm run build

      - name: Deploy til Netlify
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        run: npx netlify deploy --dir=dist --prod

Hvad gør det her anderledes?

  • To jobs: Ét til test/build, ét til deploy.
  • needs: test-and-build: Deploy kører kun, hvis tests og build lykkes.
  • if:-betingelse: Deploy job’et kører kun på direkte pushes til main, ikke på pull requests.

Det betyder, at du får:

  • CI på både pushes og pull requests (uden deploy).
  • CD kun på main, og kun når alt er grønt.

Hvis du vil være lidt mere avanceret, kan du begynde at dele ting op med matrix-builds, cache af build-output osv., men så er du også et godt stykke forbi “begynder”.

Hvor du kan bygge videre herfra

Når du først har én pipeline, der kører stabilt, er det fristende at stoppe der. Det kan du sådan set også fint. Men hvis du vil øve dig i DevOps-tankegangen, er der nogle naturlige næste trin:

  • Tilføj linting (fx npm run lint) som en ekstra step før tests.
  • Del workflows op, så nogle kun kører på PRs (fx tests), og andre kun på tags (fx release-builds).
  • Byg små tjeklister til dine projekter, som du altid kører igennem, inden du kalder noget “færdigt”.

Hvis du kommer dertil, hvor du har flere services, backend, database og det hele, kan du begynde at lege med flere jobs, artefacts og måske endda docker-containere. Så er vi ovre i noget, der minder om materiale til en helt anden artikel.

Personligt prøver jeg at holde mine små CI/CD setups mindst lige så simple som min surdej: få ingredienser, gentagelig proces, og så lader jeg være med at pille for meget, når det virker. I modsætning til surdejen har GitHub Actions dog aldrig eksploderet i mit køkken, så på den måde er det lidt tryggere at eksperimentere med.

Læg hemmeligheder i GitHub under Settings > Secrets and variables > Actions og hent dem i workflowet med expressions som ${'{'}{ secrets.MY_SECRET }{'}'}. For SSH-deploys kan du enten bruge et Deploy Key eller gemme den private nøgle som et secret og skrive den ud til en fil i et step. Del aldrig nøgler i logs, og brug environments med beskyttelse hvis du vil kræve godkendelse før deploy.
Til GitHub Pages bruger mange en færdig action som actions/configure-pages eller peaceiris/actions-gh-pages som push-er bygget output direkte til gh-pages-branchen. Til en ekstern server kan du bruge en SSH/rsync-action og lægge din private nøgle som et secret, eller bruge en hosting-udbyders CLI med et API-token. Vercel og Netlify tilbyder ofte indbygget git-integration, så du behøver kun actions hvis du vil kontrollere deploy fra workflowet.
Åbn Actions-fanen, vælg det fejlede run og læs step-logs; de viser typisk fejloutputet fra build eller test. Genkør kun det fejlede job, sæt actions' debug-flag ved at oprette secret ACTIONS_STEP_DEBUG=true, og upload relevante logs som artifacts med actions/upload-artifact for nærmere analyse.
Ja - værktøjet act (nektos/act) lader dig køre workflows lokalt ved hjælp af Docker, så du hurtigt kan iterere uden commits. Vær opmærksom på, at ikke alle tredjeparts-actions eller GitHub-metadata opfører sig fuldstændigt som på GitHub, men det er godt til hurtige tests og fejlretning.

Jonas Kirkeby har skrevet kode siden han som teenager forsøgte at lave en helt simpel hjemmeside til sin fars lille vvs-firma – og endte med at sidde oppe hele natten for at få en knap til at skifte farve. Siden da har han lært sig det meste ved at prøve sig frem, kopiere andres eksempler, ødelægge dem og langsomt forstå, hvorfor tingene virker, som de gør.

Til daglig arbejder han slet ikke med IT, men bruger aftener og morgener på små projekter: en lille side til en forening, et simpelt værktøj til at holde styr på familiens madplan eller et Python-script, der rydder op i rodede filer. Det er den slags konkrete hverdags-behov, der har formet hans måde at tænke kodning på – hvad kan jeg bygge nu, som faktisk hjælper mig eller nogen, jeg kender?

På Coding Class deler Jonas de guides, han selv ville ønske, han havde haft: korte, konkrete forløb, hvor du kan se noget på skærmen efter få minutters læsning. Han viser hele vejen fra idé til færdig løsning, inklusive de typiske fejl og små snubletråde på vejen, så du ikke kun får den pæne, polerede version.

Hans mål er, at du som begynder eller let øvet hurtigt får følelsen af: “Det her kan jeg faktisk selv finde ud af” – uanset om du vil bygge din første lille hjemmeside, forstå JavaScript-funktioner eller bruge Python til at automatisere en kedelig opgave.

Send kommentar

You May Have Missed