Å dra temafiler over FTP i 2026 ender vanligvis likt: én fil fra inc/ mangler, composer.lock kom fra en annen laptop, og produksjon ligger nede fordi noen overskrev wp-config.php. Continuous Integration og Continuous Deployment er ikke enterprise-teater - det er måten hver Git-endring går gjennom samme bygg, samme tester og samme vei inn på serveren.
Denne guiden gjelder WordPress med tema eller plugin i repoet (Composer, npm, eventuelt Docker), ikke nettsteder som bare lever i admin. Hvis egendefinert kode fortsatt bare finnes i database og media, flytt temaer og custom code til Git først - uten det har CI/CD ingenting å bygge.
Dokumentasjon for runners: GitHub Actions. Les også parallelt: Hardening WordPress. For kommandoer etter deploy: WP-CLI.
Hvorfor du ikke bør deploye WordPress via FTP
Tre problemer dukker opp igjen i norske nettbutikker og byråoverleveringer:
- Miljødrift - lokalt PHP 8.2 og Composer 2.7, VPS på PHP 8.1 uten
intl. Lokalcomposer install«fungerer»; produksjon dør på class not found. - Ingen revisjonsspor - etter en hendelse kan du ikke si hvilken commit som var live. Hostingpanelet viser en fildato, ikke en Git-SHA.
- Hemmeligheter på disk - SFTP-passord i FileZilla, eller en
.envcommitted «bare et øyeblikk», er fortsatt klassisk lekkasjevei - særlig når persondata under GDPR ligger i staging-kopier.
CI/CD fikser dette mekanisk: bygg på kjent image (shivammathur/setup-php eller egen Docker), versjoner artefakt etter SHA, deploy over SSH med nøkkel i Secrets.
Deploy WordPress med GitHub Actions
Typisk arbeidsflyt for custom-tema pluss Composer-plugins:
- Trigger - push til
maineller merge av gjennomgått pull request.developgår ofte bare til staging. - Bygg -
composer install --no-dev --optimize-autoloader,npm ci,npm run build. Pin PHP og Node i workflow (php-version: '8.3',node-version: '24'). - Test - PHPUnit for plugins, eventuelt Playwright på checkout. Fail stopper jobben; ingen deploy.
- Artefakt - pakk release uten
.git, utennode_modules, medvendor/og bygdassets/dist/. - Deploy - rsync/SCP til
releases/<run_id>/, deretter atomisk symlink-bytte.
Lås versjoner som fortsatt får rettelser. PHP 8.2 får sikkerhetsrettelser bare til 31. desember 2026, PHP 8.3 til 31. desember 2027 (støttede PHP-versjoner). Node.js 20 nådde slutten av levetiden 30. april 2026; Node.js 22 støttes til 30. april 2027 og Node.js 24 til 30. april 2028 (utgivelsesplanen for Node.js). PHP-minorversjonen følger produksjon, ikke nyeste utgivelse.
Kort skisse i GitHub Actions, med hovedversjons-tagger for lesbarhet; hvorfor produksjons-workflows låser commit-SHA, forklarer avsnittet om herding lenger ned:
name: deploy-production
on:
push:
branches: [main]
permissions:
contents: read
concurrency:
group: deploy-production
cancel-in-progress: false
jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install --no-dev --optimize-autoloader
- uses: actions/setup-node@v7
with:
node-version: '24'
cache: npm
- run: npm ci && npm run build
- name: rsync release
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
# skriv nøkkel, rsync til releases/$GITHUB_SHA, ln -sfnFull symlink-script ligger i repoet som bin/release.sh - workflowen kaller bare. Samme script fungerer fra laptop når Actions er nede.
Stopp to samtidige WordPress-deploys i GitHub Actions
To raske merges til main starter to workflow-kjøringer. Uten sperre kjører begge rsync til releases/ og bytter current i en rekkefølge ingen har valgt. En concurrency-gruppe i GitHub Actions tillater høyst én kjørende workflow per gruppe; en nylig køet kjøring venter som pending (GitHub-dokumentasjon: styre samtidighet).
To detaljer teller ved deploy. Som standard holder en gruppe én kjørende og én ventende kjøring, og en nyere kjøring avbryter den eldre ventende, så siste commit vinner og den i midten hoppes over. cancel-in-progress: true ville også avbrutt den kjørende jobben, og det kan stoppe rsync midt i en release-mappe. Behold false for produksjon, og bruk true i test-workflows på pull requests, der bare nyeste commit teller.
concurrency:
group: deploy-production
cancel-in-progress: falseSiden symlinken byttes først etter fullført rsync, rører en avbrutt ventende kjøring aldri current.
Beskytt produksjon med miljøer i GitHub Actions
Legg environment: production på deploy-jobben og konfigurer miljøet i repository-innstillingene. Da gjelder tre beskyttelser (GitHub-dokumentasjon: deployments og miljøer):
- Påkrevde reviewers: opptil seks brukere eller team, og én godkjenning slipper jobben videre. Med sperre mot selvgodkjenning kan den som startet deployen ikke godkjenne den.
- Deploy-brancher og -tagger: bare
maineller release-tagger kan deploye tilproduction, så en workflow på en feature-branch når ikke produksjonsserveren. - Miljøhemmeligheter: tilgjengelige bare for jobber som refererer til miljøet, og med påkrevde reviewers først etter godkjenning. Produksjonens
DEPLOY_SSH_KEYhører hjemme her, ikke blant repository-hemmelighetene.
Sjekk planen først. På GitHub Free, Pro og Team virker påkrevde reviewers og ventetid bare i offentlige repositories. Deploy-brancher og miljøhemmeligheter virker i private repositories på Pro og Team, men ikke på Free. Byråers WordPress-repositories er som regel private, så på disse planene finnes ikke godkjenningsporten, og branch-reglene bærer beskyttelsen.
Herd deploy-workflowen i GitHub Actions
Deploy-jobben har en SSH-nøkkel til produksjon, så workflow-filen fortjener samme gjennomgang som PHP-kode. GitHubs referanse for sikker bruk lister tiltakene som teller her:
- Lås tredjeparts actions til full commit-SHA. En tagg som
@v2kan flyttes eller slettes. Å peke en SHA om ville kreve en SHA-1-kollisjon. Hent SHA fra actionens eget repository, ikke fra en fork. GitHub nevner nå en låsefil for workflow-avhengigheter som det sterkere alternativet for repositories som tar den i bruk. - Gi
GITHUB_TOKENminimum. Settpermissions: contents: readøverst og øk per jobb bare der et steg trenger mer. En rsync-deploy over SSH trenger bare lesetilgang til repositoryet. - Hold upålitelig input unna
run:. En pull request-tittel eller et branch-navn inne i${{ }}i et shell-script åpner for skriptinjeksjon. Send verdien gjennom en mellomliggende miljøvariabel. - Én hemmelighet per verdi. Strukturerte data som en JSON-blokk kan bryte maskeringen i loggene, så lagre vert, bruker, sti og nøkkel som separate hemmeligheter.
Samme side påpeker at maskering bare gjelder hemmeligheter som brukes i jobben og er tilgjengelige for runneren, så aldri echo avledede verdier som en dekodet nøkkel.
WordPress-deploy uten nedetid med symlink
Å overskrive filer «på plass» under public_html betyr at noen requests i noen sekunder ser gammel functions.php og ny template. I WooCommerce under Black Friday-kampanjer kan det ødelegge handlekurvsesjoner.
Atomisk oppsett:
- base:
/var/www/site/ - releases:
/var/www/site/releases/20260920-142211/ - shared: uploads,
wp-config.php, object cache - utenfor releasen current: symlink til aktiv release
Etter vellykket rsync:
ln -sfn /var/www/site/releases/20260920-142211 /var/www/site/currentNginx root peker på .../current/public (eller tilsvarende). Byttene er umiddelbare. Slett gamle releases etter N dager, eller behold de tre siste for rollback.
WordPress-uploads må ligge utenfor releasen - ellers sletter hver deploy media eller kopierer gigabytes. Klassisk mønster: shared/uploads linket inn i wp-content/uploads i hver release.
WordPress staging og deploy-strategier sammenlignet
I 2026 er veien fra feature-branch via pull request og staging til main og produksjon ikke valgfri for butikker og lead-sider. Staging bør ha:
- samme PHP-versjon og utvidelser som produksjon
- databasekopi (anonymisert når persondata er i spill under GDPR)
- betalinghemmeligheter kun i sandbox
Preview-URL per PR hjelper redesign og CSS mer enn skjemamigrasjoner - de trenger fortsatt staging med ekte dump.
| Strategi | Risiko | Vedlikehold | Når det passer |
|---|---|---|---|
| Manuell FTP | Svært høy | Billig start, dyre brudd | Bare sandbox / læring |
| Git-hook på server | Middels | Lav | Lite portfolio, én utvikler |
| CI + atomisk rsync | Lav | Middels | Bedrift, WooCommerce, byrå |
| Blue/green eller to VPS | Minimal | Høy | Høy trafikk, SLA, betalinger |
Blue/green (to fulle miljøer, load balancer-bytte) lønner sjelden under hundrevis av samtidige sesjoner. De fleste norske nettbutikker klarer seg med atomisk symlink pluss mappe-rollback - typisk på Hetzner, Linode eller managed med SSH. Vipps-sandbox i staging og ekte Vipps-nøkler bare i produksjons-Secrets er et mønster som sparer mange feilaktige testkjøp.
Automatiserte tester i en CI/CD-pipeline for WordPress
En pipeline uten tester sender bare ødelagt kode ut raskere. Fire lag passer til et WordPress-repository, fra raskest til tregest:
- Enhetstester med PHPUnit sjekker enkeltfunksjoner i en utvidelse eller et tema uten database. De kjører ved hver push.
- Integrasjonstester laster WordPress mot en midlertidig testdatabase.
wp scaffold plugin-testsgenererer grunnmuren:phpunit.xml.dist,bin/install-wp-tests.shfor testpakken og databasen,tests/bootstrap.php, en eksempeltest og et regelsett.phpcs.xml.dist. Standard CI-fil er for CircleCI;--ci=githubskriver i stedet en konfigurasjon for GitHub Actions (WP-CLI: wp scaffold plugin-tests). - Statisk analyse med PHPStan og PHP_CodeSniffer fanger typefeil og brudd på kodestandarden uten å kjøre koden.
- Ende-til-ende-tester med Playwright går gjennom ekte brukerflyter: kontaktskjema, WooCommerce-handlekurv og kasse. De er det tregeste laget, så kjør dem på pull requests mot
mainog mot staging, ikke ved hver commit.
En rød jobb i hvilket som helst lag stopper workflowen før artefakt-steget, så ingenting når serveren.
Hvordan håndtere hemmeligheter og databasemigrering ved WordPress-deploy
Aldri commit:
- SSH-nøkler
- databasepassord
AUTH_KEY/SECURE_AUTH_KEYfrawp-config.php- API-tokens for betalingsgateways (Vipps, Klarna, Stripe)
I GitHub ligger de under Settings, deretter Secrets and variables, deretter Actions. Injiser som env i deploy-steget. På serveren ligger wp-config.php under shared/ og er ikke del av CI-artefakten.
WP-CLI etter symlink-bytte:
wp cache flushved object cachewp rewrite flushetter CPT-endringerwp plugin listsom smoke-linje i loggen
Kjør WP-CLI på produksjonsserveren fra runneren
Disse kommandoene må kjøre på serveren, mot den nye releasen. WP-CLI gjør det fra runneren med den globale parameteren --ssh ([<scheme>:][<user>@]<host>[:<port>][<path>]) eller med aliaser i prosjektets wp-cli.yml (WP-CLI-håndboken: kjøre kommandoer eksternt):
@staging:
ssh: [email protected]/var/www/site/current/public
@prod:
ssh: [email protected]/var/www/site/current/publicMed den filen i repositoryet kjører wp @prod cache flush på produksjon. WP-CLI må fortsatt være installert på serveren og kunne kalles som wp: runneren sender bare kommandoen videre over SSH.
Flere kommandoer verdt å legge i steget etter byttet, alle i WP-CLI-kommandoreferansen:
wp core is-installedskriver ingenting og svarer med exit-koden, så en feilet sjekk feiler steget.wp core update-db --dry-runsammenligner databaseversjonene før den ektewp core update-dbetter en kjerneoppdatering.wp core verify-checksumssammenligner kjernefilene med sjekksummene fra WordPress.org og fanger filer endret utenfor Git.wp maintenance-mode activateogdeactivatedekker den sjeldne releasen som må endre skjemaet mens ingenting skriver til det.
Planlegg databasmigrasjoner adskilt fra fil-deploy. Symlink-rollback angrer ikke ALTER TABLE. Ved destruktive migrasjoner: backup først (wp db export på staging og produksjon), deretter migrasjon, deretter trafikk til kode som trenger skjemaet - eller feature-flag i PHP.
PHP-avhengigheter: Composer install - i CI alltid --no-dev for produksjon og låst composer.lock.
Vanlige feil i en WordPress-pipeline
Fra VPS-arbeid (Hetzner, DigitalOcean, OVH) og managed med SSH:
.gitignorespiservendor/- produksjon får tema uten autoloader. Enten bygg vendor på runneren og legg den i artefakten, eller kjør Composer på serveren etter rsync (da trenger serveren Composer og Packagist-tilgang).- Feil stier - scriptet antar
/var/www/html, hosten bruker/home/user/domains/.... Pin stier i Secrets per miljø. - Opcache holder gammel kode - etter
ln -sfn, reload PHP-FPM eller kallopcache_resetvia WP-CLI/mu-plugin bare for deploy-brukeren. - Cron og køer - WooCommerce Action Scheduler kan kjøre jobber på gammel kode i noen minutter; etter store releases, se litt på failed jobs.
Smoke-test etter deploy (curl fra runner eller egen jobb):
- forsiden HTTP 200
/wp-login.php200- én kritisk produkt- eller skjemalanding
- valgfri HEAD til CDN hvis assets er content-hashed
Hvis smoke feiler, er automatisk symlink-rollback billigere enn kundeoppringing kl. 23:00.
Deploy av blokk-temaer og must-use-plugins med Git
Ikke hele WordPress passer i samme pipeline. Innhold i databasen (innlegg, ACF-feltgrupper uten JSON-eksport, Elementor-CSS) lever fortsatt utenfor releasen. CI/CD beskytter kode; det erstatter ikke databasebackup.
Praktiske skiller:
- Klassisk eller hybrid-tema med Vite - fullt CI-bygg, artefakt med
style.css+dist/. - Blokk-tema (FSE) - hold
theme.jsonog HTML-maler i Git; endringer fra Site Editor tilbake via PR, ellers divergerer produksjon og Git etter første «Lagre». - Must-use-plugins - rulles ut med releasen; oppdater dem ikke fra admin. Bra sted for deploy-healthchecks og å slå av file editor.
- Plugins fra wordpress.org - pin versjoner via Composer (
wpackagist) eller oppdater i en gjennomgått jobb. Å blande «Update i admin» med «CI overskrevplugins/» gir et tilfeldig plugin-tre.
Byrå-stacks i Norden blander ofte custom-tema, WooCommerce fra wpackagist og to premium-zip via Composer path repository. Pipelinen må kjenne alle tre kilder, ellers skiller staging og produksjon seg med én zip fra for et halvt år siden.
Sjekkliste for CI/CD og kontinuerlig deploy av WordPress
Før du kobler main til produksjon, gå gjennom listen én gang - den sparer en helg:
- Privat repo, eller bekreft historikk uten hemmeligheter (
git log -pviser ingen gamle.env). - Branch protection: obligatorisk PR, grønn
test-workflow. - Separate Secrets:
DEPLOY_HOST_STAGING,DEPLOY_HOST_PROD, egne SSH-nøkler. - Staging matcher produksjonens PHP (ikke «vi kjører 8.3, kunden har 8.1»).
- Databasebackup før første atomiske bytte - selv når kode-rollback er klar.
- Oppetidsovervåking (UptimeRobot, Better Stack e.l.) med SMS/Slack på produksjons-URL.
- Én-siders runbook: hvordan
ln -sfnmanuelt til forrige release, hvem har servertilgang.
Først da: auto-deploy fra main. Fram til da holder «bygg + test» på PR-er og manuell workflow_dispatch til staging.
Oppsummering
CI/CD for WordPress er ikke «magisk YAML». Det er en gjentakbar kontrakt: samme PHP, samme lockfile, samme artefakt, bytte uten vindu med halvveis opplastede filer. Start med Git og staging, legg til Actions for Composer/npm-bygg, avslutt med atomisk current og smoke-test. FTP blir nødverktøy, ikke prosess.
Trenger du en WordPress-utvikler som holder pipelinen sammen med temakoden for butikk eller lead-side, jobber WPPoland på ekte stack - ikke lysark.







