{"id":127,"date":"2025-10-02T00:00:00","date_gmt":"2025-10-01T22:00:00","guid":{"rendered":"https:\/\/helloblog.io\/da\/zero-downtime-wordpress-deployments-med-trellis\/"},"modified":"2026-01-20T06:33:20","modified_gmt":"2026-01-20T05:33:20","slug":"zero-downtime-wordpress-deployments-med-trellis","status":"publish","type":"post","link":"https:\/\/helloblog.io\/da\/zero-downtime-wordpress-deployments-med-trellis\/","title":{"rendered":"Zero downtime WordPress-deployments med Trellis: s\u00e5dan virker atomic releases i praksis"},"content":{"rendered":"\n<p>I moderne app-udvikling er <em>zero downtime deployments<\/em> efterh\u00e5nden baseline: nye versioner bliver gjort klar ved siden af den k\u00f8rende version, og der skiftes over i \u00e9t hug. I WordPress-verdenen ser jeg stadig masser af deploy-flows, der reelt er \u201ckopi\u00e9r filer op og kryds fingre\u201d. Roots\u2019 Trellis kommer med en atomic deployment-strategi ud af boksen, og den er en af de mest undervurderede opgraderinger, du kan give dit WordPress-driftssetup.<\/p>\n\n\n\n<p>Det interessante er, at du ikke beh\u00f8ver at adoptere Trellis til hele din udviklingsworkflow for at f\u00e5 v\u00e6rdien. Mange bruger Trellis prim\u00e6rt som deploy-v\u00e6rkt\u00f8j til Bedrock-baserede projekter, mens de forts\u00e6tter lokalt med fx Valet, Lando eller DDEV \u2013 og deployer til alt fra managed hosts til mere klassiske Linux-servere.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hvad \u201czero downtime\u201d egentlig betyder i WordPress-kontekst<\/h2>\n\n\n\n<p>N\u00e5r vi taler <em>zero downtime<\/em>, handler det ikke om, at der aldrig kan opst\u00e5 en fejl i en release. Pointen er, at selve deploy-processen ikke efterlader sitet i en halv-opdateret tilstand, hvor brugere rammer fatale errors, manglende filer eller en blanding af gammel og ny kode.<\/p>\n\n\n\n<p>Den klassiske WordPress-deploy er ofte \u201cin-place\u201d: filer overskrives direkte i den mappe, webserveren serverer fra. Det kan v\u00e6re hurtigt, men det er ogs\u00e5 den perfekte opskrift p\u00e5 transient fejl, n\u00e5r en request rammer midt i en upload.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hvorfor traditionelle WordPress-deploys g\u00e5r galt<\/h2>\n\n\n\n<p>De fleste WordPress-sites ender i \u00e9n af de her tre deploy-kategorier:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><strong>FTP\/SFTP-uploads:<\/strong> Man uploader \u00e6ndringer manuelt og overskriver eksisterende filer. Undervejs serverer sitet en blanding af gammelt og nyt.<\/li>\n\n\n<li><strong>Synkronisering med fx <code>rsync<\/code>:<\/strong> Typisk hurtigere end FTP, men det er stadig \u201coverskriv live filer\u201d, s\u00e5 problemet er det samme.<\/li>\n\n\n<li><strong>Plugin-baseret deployment p\u00e5 managed hosts:<\/strong> Ofte super nemt i UI\u2019et, men mange l\u00f8sninger opdaterer stadig filer in-place og uden en reel rollback-mekanisme.<\/li>\n\n<\/ul>\n\n\n\n<p>F\u00e6llesn\u00e6vneren er, at du kan f\u00e5 m\u00e6rkelige fejl under deploy-vinduet, og at rollback typisk er en manuel \u00f8velse (eller i praksis \u201cdeploy igen og h\u00e5b\u201d).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Trellis\u2019 l\u00f8sning: atomic og immutable deployments<\/h2>\n\n\n\n<p>Trellis deployer ikke ved at overskrive den k\u00f8rende release. I stedet arbejder den med <em>atomic deployments<\/em> (skift i \u00e9t hug) og et <em>immutable<\/em> release-princip (en release \u00e6ndres ikke efter den er lagt ud).<\/p>\n\n\n\n<p>Det betyder i praksis, at hver deploy skaber en helt ny release-mappe, g\u00f8r den klar i isolation og skifter derefter webserverens \u201cpegepind\u201d til den nye release. Ingen mellemfaser, hvor brugere ser en halvt opdateret kodebase.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Mappestrukturen: \u201ccurrent\u201d, \u201creleases\u201d og \u201cshared\u201d<\/h3>\n\n\n\n<p>N\u00e5r Trellis deployer til en server, etablerer den en struktur, der er designet til netop atomic releases:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/srv\/www\/example.com\/\n\u251c\u2500\u2500 current\/             # Symlink til aktiv release\n\u251c\u2500\u2500 releases\/            # Alle deployede releases\n\u2502   \u251c\u2500\u2500 20250930124530\/\n\u2502   \u251c\u2500\u2500 20250930083045\/\n\u2502   \u2514\u2500\u2500 20250930141622\/  # Nyeste\n\u251c\u2500\u2500 shared\/              # Delte filer p\u00e5 tv\u00e6rs af releases\n\u2502   \u2514\u2500\u2500 uploads\/\n\u2514\u2500\u2500 logs\/\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>\/srv\/www\/example.com\/<\/span><\/span>\n<span class=\"line\"><span>\u251c\u2500\u2500 current\/             # Symlink til aktiv release<\/span><\/span>\n<span class=\"line\"><span>\u251c\u2500\u2500 releases\/            # Alle deployede releases<\/span><\/span>\n<span class=\"line\"><span>\u2502   \u251c\u2500\u2500 20250930124530\/<\/span><\/span>\n<span class=\"line\"><span>\u2502   \u251c\u2500\u2500 20250930083045\/<\/span><\/span>\n<span class=\"line\"><span>\u2502   \u2514\u2500\u2500 20250930141622\/  # Nyeste<\/span><\/span>\n<span class=\"line\"><span>\u251c\u2500\u2500 shared\/              # Delte filer p\u00e5 tv\u00e6rs af releases<\/span><\/span>\n<span class=\"line\"><span>\u2502   \u2514\u2500\u2500 uploads\/<\/span><\/span>\n<span class=\"line\"><span>\u2514\u2500\u2500 logs\/<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>N\u00f8glen er <code>current<\/code>, som ikke er en \u201crigtig\u201d mappe, men et symlink (symbolsk link). Webserveren peger altid p\u00e5 <code>current<\/code>, og Trellis kan derfor skifte aktiv release ved blot at opdatere symlinket.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Hvad der sker, n\u00e5r du k\u00f8rer en deploy<\/h3>\n\n\n\n<p>N\u00e5r du k\u00f8rer <code>trellis deploy production<\/code>, gennemf\u00f8rer Trellis en serie trin, hvor den nye release bygges op adskilt fra den live kode:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n\n<li><strong>Initialize:<\/strong> Struktur sikres, og en ny release-mappe oprettes (typisk timestamp-baseret).<\/li>\n\n\n<li><strong>Update:<\/strong> Nyeste kode hentes fra dit Git-repository til et separat omr\u00e5de, ikke direkte ind i live path.<\/li>\n\n\n<li><strong>Prepare:<\/strong> Koden kopieres over i release-mappen.<\/li>\n\n\n<li><strong>Build:<\/strong> Dependencies installeres (fx via <code>composer install<\/code>).<\/li>\n\n\n<li><strong>Share:<\/strong> Delte paths (typisk uploads) linkes ind fra <code>shared<\/code> til den nye release.<\/li>\n\n\n<li><strong>Finalize:<\/strong> <code>current<\/code>-symlinket opdateres til at pege p\u00e5 den nye release.<\/li>\n\n<\/ol>\n\n\n\n<p>Overgangen bliver dermed et \u00f8jebliksskift: det ene sekund serverer sitet fra en gammel release-mappe, og det n\u00e6ste sekund serverer det fra den nye.<\/p>\n\n\n\n<div class=\"wp-block-group callout callout-info is-style-info is-layout-flow wp-block-group-is-layout-flow\" style=\"border-width:1px;border-radius:8px;padding-top:1rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem\">\n\n<h4 class=\"wp-block-heading callout-title\">Hvorfor \u201cshared\u201d betyder noget<\/h4>\n\n\n<p>WordPress er ikke kun kode. Medieuploads skal overleve p\u00e5 tv\u00e6rs af releases, og derfor holdes typisk <code>uploads\/<\/code> uden for releases og symlinkes ind. Det er en central detalje i at g\u00f8re releases reelt udskiftelige.<\/p>\n\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Database: zero downtime for kode er ikke det samme som schema-\u00e6ndringer<\/h2>\n\n\n\n<p>Atomic deployments l\u00f8ser fil- og kodeproblemet, men databasen er en separat udfordring. Trellis\u2019 deploy inkluderer ikke database-migrations som en automatisk del af processen, hvilket ogs\u00e5 er tydeligt n\u00e6vnt i Trellis-dokumentationen.<\/p>\n\n\n\n<p>Hvis du arbejder med Acorn, kan du bruge Laravel migrations (migrations = versionsstyrede schema-\u00e6ndringer) til WordPress-sites og s\u00f8rge for, at de bliver k\u00f8rt i forbindelse med deployment. Pointen er, at du stadig skal t\u00e6nke i <em>backwards compatible<\/em> schema-\u00e6ndringer, hvis du vil holde en \u201cingen nedetid\u201d-oplevelse hele vejen igennem.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Rollback p\u00e5 sekunder: den praktiske gevinst ved immutable releases<\/h2>\n\n\n\n<p>N\u00e5r hver release ligger i sin egen mappe og aldrig modificeres efter deploy, bliver rollback pludselig trivielt: du peger bare <code>current<\/code> tilbage p\u00e5 den forrige release.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>trellis rollback production\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#B392F0\">trellis<\/span><span style=\"color:#9ECBFF\"> rollback<\/span><span style=\"color:#9ECBFF\"> production<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Trellis beholder som standard flere af de seneste releases p\u00e5 serveren, s\u00e5 rollback ikke kr\u00e6ver en ny build eller re-upload. Det er i praksis en af de st\u00f8rste forbedringer i driftssikkerhed, du kan f\u00e5 for WordPress.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Deploy hooks: g\u00f8r deployment til et kontrolleret flow<\/h2>\n\n\n\n<p>Trellis har et s\u00e6t hooks (hooks = indstikspunkter i processen), s\u00e5 du kan koble egne tasks p\u00e5 f\u00f8r\/efter bestemte deploy-trin. Blandt andet findes der hooks til build- og finalize-faserne, og derudover hooks omkring de st\u00f8rre deploy steps (initialize, update, prepare, build, share, finalize).<\/p>\n\n\n\n<p>I praksis betyder det, at du kan standardisere ting, der ellers ofte ligger som manuelle tjeklister:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Tage database-backup lige inden der skiftes release<\/li>\n\n\n<li>Rydde caches efter deploy (page cache, object cache osv.)<\/li>\n\n\n<li>Sende en deployment-notifikation til teamet<\/li>\n\n\n<li>K\u00f8re simple smoke tests mod den nye release<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Kom i gang: minimal opskrift til zero downtime deploys<\/h2>\n\n\n\n<p>Hvis du vil i gang uden at vende hele din udviklingsstack p\u00e5 hovedet, er den korte vej typisk:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n\n<li>Brug Bedrock for en mere robust projektstruktur og dependency management.<\/li>\n\n\n<li>Install\u00e9r Trellis og konfigur\u00e9r dine deployment-indstillinger.<\/li>\n\n\n<li>Udfyld <code>wordpress_sites.yml<\/code> med dit Git-repository (og relevante deploy-settings).<\/li>\n\n\n<li>K\u00f8r <code>trellis deploy production<\/code>.<\/li>\n\n<\/ol>\n\n\n\n<p>F\u00f8rste deploy tager typisk l\u00e6ngst tid, fordi serverens struktur og dependencies skal etableres. Derefter handler det prim\u00e6rt om, at hver release bygges isoleret og aktiveres atomisk.<\/p>\n\n\n\n<div class=\"wp-block-group callout callout-success is-style-success is-layout-flow wp-block-group-is-layout-flow\" style=\"border-width:1px;border-radius:8px;padding-top:1rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem\">\n\n<h4 class=\"wp-block-heading callout-title\">Det vigtigste take-away<\/h4>\n\n\n<p>Hvis du kan skifte fra \u201coverskriv filer p\u00e5 live site\u201d til \u201cbyg en release og skift symlink\u201d, f\u00e5r du b\u00e5de f\u00e6rre deploy-fejl og en rollback, der faktisk er brugbar i praksis.<\/p>\n\n<\/div>\n\n\n<div class=\"references-section\">\n                <h2>Referencer \/ Kilder<\/h2>\n                <ul class=\"references-list\"><li><a href=\"https:\/\/roots.io\/zero-downtime-wordpress-deployments-with-trellis\/\" target=\"_blank\" rel=\"noopener noreferrer\">Zero Downtime WordPress Deployments with Trellis<\/a><\/li><li><a href=\"https:\/\/roots.io\/trellis\/docs\/deployments\/\" target=\"_blank\" rel=\"noopener noreferrer\">Trellis documentation: Deployments<\/a><\/li><li><a href=\"https:\/\/roots.io\/bedrock\/\" target=\"_blank\" rel=\"noopener noreferrer\">Bedrock<\/a><\/li><li><a href=\"https:\/\/roots.io\/trellis\/\" target=\"_blank\" rel=\"noopener noreferrer\">Trellis<\/a><\/li><li><a href=\"https:\/\/roots.io\/acorn\/docs\/creating-and-running-laravel-migrations\/\" target=\"_blank\" rel=\"noopener noreferrer\">Acorn documentation: Creating and running Laravel migrations<\/a><\/li><\/ul>\n            <\/div>","protected":false},"excerpt":{"rendered":"<p>Hvis du stadig deployer WordPress ved at overskrive filer p\u00e5 en live server, har du sikkert set de klassiske \u201clige i et \u00f8jeblik\u201d-fejl. Trellis l\u00f8ser det med atomic deployments og rollback p\u00e5 sekunder \u2013 uden at du beh\u00f8ver at skifte hele dit lokale setup ud.<\/p>\n","protected":false},"author":64,"featured_media":126,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22],"tags":[67,72,73,71,10],"class_list":["post-127","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wordpress-okosystem","tag-bedrock","tag-deployments","tag-devops","tag-trellis","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/posts\/127","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/users\/64"}],"replies":[{"embeddable":true,"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/comments?post=127"}],"version-history":[{"count":1,"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/posts\/127\/revisions"}],"predecessor-version":[{"id":143,"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/posts\/127\/revisions\/143"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/media\/126"}],"wp:attachment":[{"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/media?parent=127"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/categories?post=127"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/helloblog.io\/da\/wp-json\/wp\/v2\/tags?post=127"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}